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.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.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..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 @@ -33,7 +33,10 @@ public class BattlesRunner { private final int numrounds; private final int fieldlen; 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; @@ -45,6 +48,10 @@ 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"); + if (hidenames) { + resultSettings.append(",HIDENAMES=YES"); + } outfile = parameters.getProperty("OUTPUT", ""); user = parameters.getProperty("USER", ""); this.game = game; @@ -97,8 +104,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); @@ -171,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 a5b55db04..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 @@ -171,7 +171,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] + + (hasSetting(header, "HIDENAMES=YES") ? "&hidenames=YES" : ""); String data = "game=" + game + commonData; @@ -233,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")); + } +} 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"); + } + } +}