From 7d032e5481fb9f2fe9872dcf1fe75d0106dffdea Mon Sep 17 00:00:00 2001 From: Chase-san <34535+Chase-san@users.noreply.github.com> Date: Sat, 31 Oct 2026 19:15:34 -0400 Subject: [PATCH 1/3] Add HIDENAMES to the RoboRumble client The engine, the battle dialog and .battle files can already hide enemy names from robots, but the rumble client always ran battles with names shown, so a competition could not use it. With HIDENAMES=YES in a rumble's configuration, the client runs its battles with hidden names and adds hidenames=YES to each upload, so a server can refuse results from a client that did not. The default is NOT. --- .../src/main/resources/roborumble/meleerumble.txt | 6 ++++++ .../src/main/resources/roborumble/roborumble.txt | 6 ++++++ .../src/main/resources/roborumble/teamrumble.txt | 6 ++++++ .../src/main/resources/roborumble/twinduel.txt | 6 ++++++ .../robocode/roborumble/battlesengine/BattlesRunner.java | 7 +++++-- .../sf/robocode/roborumble/netengine/ResultsUpload.java | 5 ++++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/robocode.content/src/main/resources/roborumble/meleerumble.txt b/robocode.content/src/main/resources/roborumble/meleerumble.txt index 7f289e8f6..0808271fd 100644 --- a/robocode.content/src/main/resources/roborumble/meleerumble.txt +++ b/robocode.content/src/main/resources/roborumble/meleerumble.txt @@ -86,6 +86,12 @@ NUMBATTLES=3 ROUNDS=35 MELEEBOTS=10 +# HIDENAMES +# If set to YES, robots only see their enemies by anonymous names like +# #1 instead of by their real names. Use YES or NOT. + +HIDENAMES=NOT + # INPUT Input battles file that is generated by the rumble automatically. # The rumble uses this file for selecting which robots that must # battle against each other. diff --git a/robocode.content/src/main/resources/roborumble/roborumble.txt b/robocode.content/src/main/resources/roborumble/roborumble.txt index ed29b1a92..bb7bfab1e 100644 --- a/robocode.content/src/main/resources/roborumble/roborumble.txt +++ b/robocode.content/src/main/resources/roborumble/roborumble.txt @@ -83,6 +83,12 @@ FIELDH=600 NUMBATTLES=50 ROUNDS=35 +# HIDENAMES +# If set to YES, robots only see their enemies by anonymous names like +# #1 instead of by their real names. Use YES or NOT. + +HIDENAMES=NOT + # INPUT Input battles file that is generated by the rumble automatically. # The rumble uses this file for selecting which robots that must # battle against each other. diff --git a/robocode.content/src/main/resources/roborumble/teamrumble.txt b/robocode.content/src/main/resources/roborumble/teamrumble.txt index 0fd47aa1f..e90e0c12c 100644 --- a/robocode.content/src/main/resources/roborumble/teamrumble.txt +++ b/robocode.content/src/main/resources/roborumble/teamrumble.txt @@ -83,6 +83,12 @@ FIELDH=1200 NUMBATTLES=10 ROUNDS=10 +# HIDENAMES +# If set to YES, robots only see their enemies by anonymous names like +# #1 instead of by their real names. Use YES or NOT. + +HIDENAMES=NOT + # INPUT Input battles file that is generated by the rumble automatically. # The rumble uses this file for selecting which robots that must # battle against each other. diff --git a/robocode.content/src/main/resources/roborumble/twinduel.txt b/robocode.content/src/main/resources/roborumble/twinduel.txt index a3a6de71c..030f5fd2f 100644 --- a/robocode.content/src/main/resources/roborumble/twinduel.txt +++ b/robocode.content/src/main/resources/roborumble/twinduel.txt @@ -83,6 +83,12 @@ FIELDH=800 NUMBATTLES=10 ROUNDS=75 +# HIDENAMES +# If set to YES, robots only see their enemies by anonymous names like +# #1 instead of by their real names. Use YES or NOT. + +HIDENAMES=NOT + # INPUT Input battles file that is generated by the rumble automatically. # The rumble uses this file for selecting which robots that must # battle against each other. 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..b9be50260 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 @@ -33,6 +33,7 @@ public class BattlesRunner { private final int numrounds; private final int fieldlen; private final int fieldhei; + private final boolean hidenames; private final String outfile; private final String user; private final String game; @@ -45,6 +46,7 @@ public BattlesRunner(String game, Properties parameters) { numrounds = Integer.parseInt(parameters.getProperty("ROUNDS", "10")); fieldlen = Integer.parseInt(parameters.getProperty("FIELDL", "800")); fieldhei = Integer.parseInt(parameters.getProperty("FIELDH", "600")); + hidenames = parameters.getProperty("HIDENAMES", "NOT").equals("YES"); outfile = parameters.getProperty("OUTPUT", ""); user = parameters.getProperty("USER", ""); this.game = game; @@ -97,8 +99,9 @@ public void runBattlesImpl(boolean melee) { final String teamLast = robotsList[robotsList.length - 1].getTeamId(); if (team0 == null || !team0.equals(teamLast)) { - final BattleSpecification specification = new BattleSpecification(battle.getNumRounds(), - battle.getBattlefield(), robotsList); + final BattleSpecification specification = new BattleSpecification(battle.getBattlefield(), + battle.getNumRounds(), battle.getInactivityTime(), battle.getGunCoolingRate(), + battle.getSentryBorderSize(), hidenames, robotsList); lastResults = null; engine.runBattle(specification, true); 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..8daf368ab 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 @@ -43,6 +43,7 @@ public class ResultsUpload { private final String priority; private final String teams; private final String melee; + private final boolean hidenames; public ResultsUpload(String game, Properties parameters, String clientVersion) { @@ -62,6 +63,7 @@ public ResultsUpload(String game, Properties parameters, String clientVersion) { client = clientVersion; teams = parameters.getProperty("TEAMS", "NOT"); melee = parameters.getProperty("MELEE", "NOT"); + hidenames = parameters.getProperty("HIDENAMES", "NOT").equals("YES"); // Open competitions selector size = new CompetitionsSelector(sizesfile, botsrepository); @@ -171,7 +173,8 @@ public void uploadResults() { final String commonData = "&version=1" + "&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]; + + second[0] + "&sscore=" + second[1] + "&sbulletd=" + second[2] + "&ssurvival=" + second[3] + + (hidenames ? "&hidenames=YES" : ""); String data = "game=" + game + commonData; From 2f54c5b7f24a87e9f8b36bf1481f4f292332c2af Mon Sep 17 00:00:00 2001 From: Chase-san <34535+Chase-san@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:08:54 -0400 Subject: [PATCH 2/3] Keep one object for each robot's hidden name A hidden name was built again on every call, so each event carried a new String. Real names are one object, and many robots compare them with == or !=. --- .../net/sf/robocode/host/RobotStatics.java | 7 +- .../main/java/tested/robots/HiddenNames.java | 40 +++++++++++ .../robocode/test/robots/TestHiddenNames.java | 67 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 robocode.tests.robots/src/main/java/tested/robots/HiddenNames.java create mode 100644 robocode.tests/src/test/java/net/sf/robocode/test/robots/TestHiddenNames.java diff --git a/robocode.core/src/main/java/net/sf/robocode/host/RobotStatics.java b/robocode.core/src/main/java/net/sf/robocode/host/RobotStatics.java index 241753495..f4722a1ca 100644 --- a/robocode.core/src/main/java/net/sf/robocode/host/RobotStatics.java +++ b/robocode.core/src/main/java/net/sf/robocode/host/RobotStatics.java @@ -56,12 +56,16 @@ public final class RobotStatics implements IRobotStatics, Serializable { private final int robotIndex; private final int teamIndex; + // One object, like the real name, because many robots compare enemy names with == or != + private final String annonymousName; + public RobotStatics(RobotSpecification robotSpecification, String name, String suffix, boolean isLeader, BattleRules rules, String teamName, List teamMembers, int robotIndex, int teamIndex) { IRobotItem robotItem = (IRobotItem) HiddenAccess.getFileSpecification(robotSpecification); this.robotIndex = robotIndex; this.teamIndex = teamIndex; + this.annonymousName = "#" + robotIndex; this.shortClassName = robotItem.getShortClassName(); this.fullClassName = robotItem.getFullClassName(); @@ -120,6 +124,7 @@ private RobotStatics(String robocodeVersion, boolean isJuniorRobot, boolean isIn this.teamName = teamName; this.robotIndex = robotIndex; this.teamIndex = teamIndex; + this.annonymousName = "#" + robotIndex; } public String getRobocodeVersion() { @@ -127,7 +132,7 @@ public String getRobocodeVersion() { } public String getAnnonymousName() { - return "#" + robotIndex; + return annonymousName; } public boolean isJuniorRobot() { diff --git a/robocode.tests.robots/src/main/java/tested/robots/HiddenNames.java b/robocode.tests.robots/src/main/java/tested/robots/HiddenNames.java new file mode 100644 index 000000000..a8f06258e --- /dev/null +++ b/robocode.tests.robots/src/main/java/tested/robots/HiddenNames.java @@ -0,0 +1,40 @@ +/* + * 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 tested.robots; + + +import robocode.AdvancedRobot; +import robocode.ScannedRobotEvent; + + +/** + * Reports the enemy names it sees, and whether each name is the same object as the last one. + * + * @author Robert Maupin (original) + */ +public class HiddenNames extends AdvancedRobot { + private String lastName; + + @Override + public void run() { + while (true) { + turnRadarRight(360); + } + } + + @Override + public void onScannedRobot(ScannedRobotEvent e) { + String name = e.getName(); + + out.println("Enemy name: " + name); + if (lastName != null && name != lastName) { + out.println("The name is a new object"); + } + lastName = name; + } +} diff --git a/robocode.tests/src/test/java/net/sf/robocode/test/robots/TestHiddenNames.java b/robocode.tests/src/test/java/net/sf/robocode/test/robots/TestHiddenNames.java new file mode 100644 index 000000000..3cd99f9f2 --- /dev/null +++ b/robocode.tests/src/test/java/net/sf/robocode/test/robots/TestHiddenNames.java @@ -0,0 +1,67 @@ +/* + * 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.test.robots; + + +import net.sf.robocode.test.helpers.Assert; +import net.sf.robocode.test.helpers.RobocodeTestBed; +import org.junit.Test; +import robocode.control.BattleSpecification; +import robocode.control.RobotSpecification; +import robocode.control.events.TurnEndedEvent; + + +/** + * Many robots compare enemy names with == or !=, which works for real names because a robot's name is one + * object. Hidden names must behave the same, or those robots lose track of their enemies. + * + * @author Robert Maupin (original) + */ +public class TestHiddenNames extends RobocodeTestBed { + private boolean sawHiddenName; + + @Test + public void run() { + super.run(); + Assert.assertTrue("The robot never saw a hidden name", sawHiddenName); + } + + @Override + public String getRobotName() { + return "tested.robots.HiddenNames"; + } + + @Override + public String getEnemyName() { + return "sample.Target"; + } + + @Override + protected void runBattle(String robotList, int numRounds, String initialPositions) { + RobotSpecification[] robots = engine.getLocalRepository(robotList); + BattleSpecification battle = new BattleSpecification(battleFieldSpec, numRounds, 450, 0.1, 100, true, robots); + + engine.runBattle(battle, initialPositions, true, false); + } + + @Override + public void onTurnEnded(TurnEndedEvent event) { + super.onTurnEnded(event); + String out = event.getTurnSnapshot().getRobots()[0].getOutputStreamSnapshot(); + + if (out.contains("Enemy name: #")) { + sawHiddenName = true; + } + if (out.contains("sample.Target")) { + Assert.fail("The robot saw the enemy's real name"); + } + if (out.contains("The name is a new object")) { + Assert.fail("The hidden name was not the same object each time"); + } + } +} From 9d2118ff8f575be5815f3cdbb15b522971a60abb Mon Sep 17 00:00:00 2001 From: Chase-san <34535+Chase-san@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:22:20 -0400 Subject: [PATCH 3/3] Save HIDENAMES with each battle's results The upload read HIDENAMES 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 --- robocode.roborumble/build.gradle.kts | 1 + .../battlesengine/BattlesRunner.java | 7 +++- .../roborumble/netengine/ResultsUpload.java | 17 ++++++-- .../netengine/ResultsUploadTest.java | 39 +++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 robocode.roborumble/src/test/java/net/sf/robocode/roborumble/netengine/ResultsUploadTest.java 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 b9be50260..ce37db5b5 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 @@ -35,6 +35,8 @@ public class BattlesRunner { private final int fieldhei; private final boolean hidenames; 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 static RobotResults[] lastResults; @@ -47,6 +49,9 @@ public BattlesRunner(String game, Properties parameters) { fieldlen = Integer.parseInt(parameters.getProperty("FIELDL", "800")); fieldhei = Integer.parseInt(parameters.getProperty("FIELDH", "600")); hidenames = parameters.getProperty("HIDENAMES", "NOT").equals("YES"); + if (hidenames) { + resultSettings.append(",HIDENAMES=YES"); + } outfile = parameters.getProperty("OUTPUT", ""); user = parameters.getProperty("USER", ""); this.game = game; @@ -174,7 +179,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 8daf368ab..846c09692 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 @@ -43,7 +43,6 @@ public class ResultsUpload { private final String priority; private final String teams; private final String melee; - private final boolean hidenames; public ResultsUpload(String game, Properties parameters, String clientVersion) { @@ -63,7 +62,6 @@ public ResultsUpload(String game, Properties parameters, String clientVersion) { client = clientVersion; teams = parameters.getProperty("TEAMS", "NOT"); melee = parameters.getProperty("MELEE", "NOT"); - hidenames = parameters.getProperty("HIDENAMES", "NOT").equals("YES"); // Open competitions selector size = new CompetitionsSelector(sizesfile, botsrepository); @@ -174,7 +172,7 @@ public void uploadResults() { + "&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] - + (hidenames ? "&hidenames=YES" : ""); + + (hasSetting(header, "HIDENAMES=YES") ? "&hidenames=YES" : ""); String data = "game=" + game + commonData; @@ -236,6 +234,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")); + } +}