Difference between revisions of "User:AaronR/HatLeagueRobot"

From Robowiki
Jump to navigation Jump to search
(Publishing some source code for the Hat League)
 
m (Using <syntaxhighlight>.)
 
(One intermediate revision by one other user not shown)
Line 2: Line 2:
  
 
== Version history ==
 
== Version history ==
 +
* '''0.2''' ''The "I didn't even expect it to work this well" release.''
 +
** '''Release: 2008-01-15'''
 +
** Bugfix: the enemy-scanned event didn't bother to check whether the scanned robot was actually an enemy...
 +
** The API has been changed to pass bullet power instead of bullet speed as the parameter to the partner-bullet-fired and enemy-wave-fired events.
 +
** The internal implementation now properly handles multiple messages that are merged into a single string (separated by "\n"). It will also ignore blank messages.
 +
** Added hlGetRadarTarget() and hlGetBulletTarget() methods that return the values previously supplied through the corresponding hlSetRadarTarget() and hlSetBulletTarget() methods.
 +
 
* '''0.1''' ''The "This should save some time" release.''
 
* '''0.1''' ''The "This should save some time" release.''
 
** '''Release: 2008-01-14'''
 
** '''Release: 2008-01-14'''
Line 8: Line 15:
 
==  What's next ==
 
==  What's next ==
 
* Debugging, probably.
 
* Debugging, probably.
 +
* Make it sort out duplicate enemy-scanned and enemy-fired-wave events registered by both robots.
 
* Better enemy wave detection.
 
* Better enemy wave detection.
  
Line 16: Line 24:
  
 
=== The HatLeagueRobot class ===
 
=== The HatLeagueRobot class ===
<pre>
+
<syntaxhighlight>
 
package wiki.hat;
 
package wiki.hat;
  
Line 24: Line 32:
 
import java.awt.geom.Point2D;
 
import java.awt.geom.Point2D;
 
import java.io.IOException;
 
import java.io.IOException;
import java.util.HashMap;
+
import java.util.*;
import java.util.Map;
 
  
 
import robocode.*;
 
import robocode.*;
Line 79: Line 86:
  
 
public void hlOnPartnerBulletOriginReceived(long time,
 
public void hlOnPartnerBulletOriginReceived(long time,
Point2D.Double location, double heading, double velocity) {
+
Point2D.Double location, double heading, double power) {
 
}
 
}
  
Line 87: Line 94:
 
*/
 
*/
 
public void hlOnEnemyWaveOriginReceived(long time, Point2D.Double location,
 
public void hlOnEnemyWaveOriginReceived(long time, Point2D.Double location,
double velocity) {
+
double power) {
 
}
 
}
  
Line 133: Line 140:
 
return null;
 
return null;
 
}
 
}
+
 
 
double realBulletPower = min(getEnergy(), min(max(bulletPower,
 
double realBulletPower = min(getEnergy(), min(max(bulletPower,
 
Rules.MIN_BULLET_POWER), Rules.MAX_BULLET_POWER));
 
Rules.MIN_BULLET_POWER), Rules.MAX_BULLET_POWER));
Line 144: Line 151:
 
return setFireBullet(realBulletPower);
 
return setFireBullet(realBulletPower);
 
}
 
}
 +
 +
private String radarTarget = null;
 +
private String bulletTarget = null;
  
 +
public final String hlGetRadarTarget() {
 +
return radarTarget;
 +
}
 +
 
public final void hlSetRadarTarget(String target) {
 
public final void hlSetRadarTarget(String target) {
 
broadcastHatLeagueMessage(new HatLeagueMessage("MR", target));
 
broadcastHatLeagueMessage(new HatLeagueMessage("MR", target));
 +
radarTarget = target;
 +
}
 +
 +
public final String hlGetBulletTarget() {
 +
return bulletTarget;
 
}
 
}
  
 
public final void hlSetBulletTarget(String target) {
 
public final void hlSetBulletTarget(String target) {
 
broadcastHatLeagueMessage(new HatLeagueMessage("MB", target));
 
broadcastHatLeagueMessage(new HatLeagueMessage("MB", target));
 +
bulletTarget = target;
 
}
 
}
  
Line 180: Line 200:
 
}
 
}
  
private HatLeagueMessage decodeHatLeagueMessage(String message) {
+
private HatLeagueMessage[] decodeHatLeagueMessages(String receivedBroadcast) {
String[] messageParts = message.split(",");
+
String[] messages = receivedBroadcast.split("\n");
// Dang it! Why did they have to wait until Java 1.6 to introduce
+
List<HatLeagueMessage> decodedMessages = new ArrayList<HatLeagueMessage>();
// Arrays.copyOfRange()?
+
for (String message : messages) {
String[] valueParts = new String[messageParts.length - 1];
+
if (!message.equals("")) {
for (int i = 1; i < messageParts.length; i++) {
+
String[] messageParts = message.split(",");
valueParts[i - 1] = messageParts[i];
+
// Dang it! Why did they have to wait until Java 1.6 to
 +
// introduce Arrays.copyOfRange()?
 +
String[] valueParts = new String[messageParts.length - 1];
 +
for (int i = 1; i < messageParts.length; i++) {
 +
valueParts[i - 1] = messageParts[i];
 +
}
 +
 
 +
decodedMessages.add(new HatLeagueMessage(messageParts[0],
 +
valueParts));
 +
}
 
}
 
}
  
return new HatLeagueMessage(messageParts[0], valueParts);
+
return decodedMessages.toArray(new HatLeagueMessage[decodedMessages.size()]);
 
}
 
}
  
Line 237: Line 266:
 
public final void onScannedRobot(ScannedRobotEvent e) {
 
public final void onScannedRobot(ScannedRobotEvent e) {
 
String name = e.getName();
 
String name = e.getName();
long time = getTime();
+
if (!isTeammate(name)) {
double distance = e.getDistance();
+
long time = getTime();
Point2D.Double location = project(new Point2D.Double(getX(), getY()),
+
double distance = e.getDistance();
e.getBearingRadians(), distance);
+
Point2D.Double location = project(
double heading = e.getHeadingRadians();
+
new Point2D.Double(getX(), getY()), e.getBearingRadians(),
double velocity = e.getVelocity();
+
distance);
double energy = e.getEnergy();
+
double heading = e.getHeadingRadians();
 +
double velocity = e.getVelocity();
 +
double energy = e.getEnergy();
  
HatLeagueMessage scannedMessage = new HatLeagueMessage("ES",
+
HatLeagueMessage scannedMessage = new HatLeagueMessage("ES",
Long.toString(time), Double.toString(location.getX()),
+
Long.toString(time), Double.toString(location.getX()),
Double.toString(location.getY()), Double.toString(heading),
+
Double.toString(location.getY()), Double.toString(heading),
Double.toString(velocity), Double.toString(energy), name);
+
Double.toString(velocity), Double.toString(energy), name);
passHatLeagueMessage(scannedMessage);
+
passHatLeagueMessage(scannedMessage);
broadcastHatLeagueMessage(scannedMessage);
+
broadcastHatLeagueMessage(scannedMessage);
  
if (enemyEnergies.containsKey(name)) {
+
if (enemyEnergies.containsKey(name)) {
double lastEnergy = enemyEnergies.get(name);
+
double lastEnergy = enemyEnergies.get(name);
double difference = lastEnergy - energy;
+
double difference = lastEnergy - energy;
if (difference > 0.09 && difference < 3.01) {
+
if (difference > 0.09 && difference < 3.01) {
HatLeagueMessage waveMessage = new HatLeagueMessage("EW",
+
HatLeagueMessage waveMessage = new HatLeagueMessage("EW",
Long.toString(time), Double.toString(location.getX()),
+
Long.toString(time),
Double.toString(location.getY()),
+
Double.toString(location.getX()),
Double.toString(getBulletSpeed(difference)));
+
Double.toString(location.getY()),
passHatLeagueMessage(waveMessage);
+
Double.toString(getBulletSpeed(difference)));
broadcastHatLeagueMessage(waveMessage);
+
passHatLeagueMessage(waveMessage);
 +
broadcastHatLeagueMessage(waveMessage);
 +
}
 
}
 
}
 +
 +
enemyEnergies.put(name, energy);
 
}
 
}
 
enemyEnergies.put(name, energy);
 
 
}
 
}
  
 
@Override
 
@Override
 
public final void onMessageReceived(MessageEvent e) {
 
public final void onMessageReceived(MessageEvent e) {
passHatLeagueMessage(decodeHatLeagueMessage(e.getMessage().toString()));
+
HatLeagueMessage[] messages = decodeHatLeagueMessages(e.getMessage().toString());
 +
for (HatLeagueMessage message : messages) {
 +
passHatLeagueMessage(message);
 +
}
 
}
 
}
  
Line 291: Line 327:
 
Double.parseDouble(messageValues[5]), messageValues[6]);
 
Double.parseDouble(messageValues[5]), messageValues[6]);
 
} else if (messageType.equals("BO")) {
 
} else if (messageType.equals("BO")) {
hlOnPartnerBulletOriginReceived(Long.parseLong(messageValues[0]),
+
hlOnPartnerBulletOriginReceived(
 +
Long.parseLong(messageValues[0]),
 
new Point2D.Double(Double.parseDouble(messageValues[1]),
 
new Point2D.Double(Double.parseDouble(messageValues[1]),
 
Double.parseDouble(messageValues[2])),
 
Double.parseDouble(messageValues[2])),
 
Double.parseDouble(messageValues[3]),
 
Double.parseDouble(messageValues[3]),
Double.parseDouble(messageValues[4]));
+
getBulletPowerFromSpeed(Double.parseDouble(messageValues[4])));
 
} else if (messageType.equals("EW")) {
 
} else if (messageType.equals("EW")) {
hlOnEnemyWaveOriginReceived(Long.parseLong(messageValues[0]),
+
hlOnEnemyWaveOriginReceived(
 +
Long.parseLong(messageValues[0]),
 
new Point2D.Double(Double.parseDouble(messageValues[1]),
 
new Point2D.Double(Double.parseDouble(messageValues[1]),
 
Double.parseDouble(messageValues[2])),
 
Double.parseDouble(messageValues[2])),
Double.parseDouble(messageValues[3]));
+
getBulletPowerFromSpeed(Double.parseDouble(messageValues[3])));
 
} else if (messageType.equals("MR")) {
 
} else if (messageType.equals("MR")) {
 
hlOnPartnerRadarTargetReceived(messageValues[0]);
 
hlOnPartnerRadarTargetReceived(messageValues[0]);
Line 325: Line 363:
 
+ messageType);
 
+ messageType);
 
}
 
}
 +
}
 +
 +
private static double getBulletPowerFromSpeed(double bulletSpeed) {
 +
return (20.0 - bulletSpeed) / 3;
 
}
 
}
  
Line 334: Line 376:
 
}
 
}
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 
=== Example robot ===
 
=== Example robot ===
<pre>
+
<syntaxhighlight>
 
package wiki.hat;
 
package wiki.hat;
  
Line 362: Line 404:
 
}
 
}
 
}
 
}
</pre>
+
</syntaxhighlight>

Latest revision as of 09:36, 1 July 2010

Yet another base class for robots participating in the Hat League.

Version history

  • 0.2 The "I didn't even expect it to work this well" release.
    • Release: 2008-01-15
    • Bugfix: the enemy-scanned event didn't bother to check whether the scanned robot was actually an enemy...
    • The API has been changed to pass bullet power instead of bullet speed as the parameter to the partner-bullet-fired and enemy-wave-fired events.
    • The internal implementation now properly handles multiple messages that are merged into a single string (separated by "\n"). It will also ignore blank messages.
    • Added hlGetRadarTarget() and hlGetBulletTarget() methods that return the values previously supplied through the corresponding hlSetRadarTarget() and hlSetBulletTarget() methods.
  • 0.1 The "This should save some time" release.
    • Release: 2008-01-14
    • A largely untested and probably buggy robot framework.

What's next

  • Debugging, probably.
  • Make it sort out duplicate enemy-scanned and enemy-fired-wave events registered by both robots.
  • Better enemy wave detection.

Source code

Usage rules

  1. If a method begins with "hl", use it instead of the normal Robocode method.
  2. See rule 1.

The HatLeagueRobot class

package wiki.hat;

import static java.lang.Math.*;
import static robocode.Rules.*;

import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.*;

import robocode.*;

public abstract class HatLeagueRobot extends TeamRobot {
	// OVERRIDE THESE IN SUBCLASSES, NOT THE NORMAL ROBOCODE METHODS! //

	/**
	 * Don't:
	 * 
	 * public void hlRun() {
	 * 	// ...
	 * 
	 * 	do {
	 * 		// ...
	 * 		execute();
	 * 	} while (true);
	 * }
	 * 
	 * Do:
	 * 
	 * public void hlRun() {
	 * 	// ...
	 * }
	 * 
	 * public void hlOnTickEnd() {
	 * 	// ...
	 * }
	 */
	public void hlRun() {
	}

	public void hlOnTickBegin() {
	}

	public void hlOnTickEnd() {
	}

	public void hlOnCustomEvent(CustomEvent e) {
	}

	public void hlOnPartnerDataReceived(long time, Point2D.Double location,
			double heading, double velocity, double energy) {
	}

	/**
	 * Note that this method is called both when this robot scans an enemy and
	 * when the partner scans one.
	 */
	public void hlOnEnemyDataReceived(long time, Point2D.Double location,
			double heading, double velocity, double energy, String name) {
	}

	public void hlOnPartnerBulletOriginReceived(long time,
			Point2D.Double location, double heading, double power) {
	}

	/**
	 * Note that this method is called both when this robot scans an enemy that
	 * fired a wave and when the partner scans one.
	 */
	public void hlOnEnemyWaveOriginReceived(long time, Point2D.Double location,
			double power) {
	}

	public void hlOnPartnerRadarTargetReceived(String target) {
	}

	public void hlOnPartnerBulletTargetReceived(String target) {
	}

	public void hlOnSuggestedRadarTargetReceived(String target) {
	}

	public void hlOnSuggestedBulletTargetReceived(String target) {
	}

	public void hlOnCustomMessageReceived(String message) {
	}

	// SPECIAL HAT LEAGUE ACTION METHODS. //

	private static class HatLeagueMessage {
		private String name;
		private String[] values;

		public HatLeagueMessage(String name, String... values) {
			this.name = name;
			this.values = values;
		}

		public String getName() {
			return name;
		}

		public String[] getValues() {
			return values;
		}
	}

	/**
	 * Use this everywhere you would normally use setFire().
	 */
	public final Bullet hlSetFireBullet(double bulletPower) {
		// Copied from robocode.peer.RobotPeer.java. =)
		if (getGunHeat() > 0 || getEnergy() == 0) {
			return null;
		}

		double realBulletPower = min(getEnergy(), min(max(bulletPower,
				Rules.MIN_BULLET_POWER), Rules.MAX_BULLET_POWER));

		broadcastHatLeagueMessage(new HatLeagueMessage("BO",
				Long.toString(getTime()), Double.toString(getX()),
				Double.toString(getY()),
				Double.toString(getGunHeadingRadians()),
				Double.toString(getBulletSpeed(realBulletPower))));
		return setFireBullet(realBulletPower);
	}
	
	private String radarTarget = null;
	private String bulletTarget = null;

	public final String hlGetRadarTarget() {
		return radarTarget;
	}
	
	public final void hlSetRadarTarget(String target) {
		broadcastHatLeagueMessage(new HatLeagueMessage("MR", target));
		radarTarget = target;
	}
	
	public final String hlGetBulletTarget() {
		return bulletTarget;
	}

	public final void hlSetBulletTarget(String target) {
		broadcastHatLeagueMessage(new HatLeagueMessage("MB", target));
		bulletTarget = target;
	}

	public final void hlSuggestRadarTarget(String target) {
		broadcastHatLeagueMessage(new HatLeagueMessage("TR", target));
	}

	public final void hlSuggestBulletTarget(String target) {
		broadcastHatLeagueMessage(new HatLeagueMessage("TB", target));
	}

	public final void hlBroadcastCustomMessage(String message) {
		broadcastHatLeagueMessage(new HatLeagueMessage("NA", message));
	}

	private void broadcastHatLeagueMessage(HatLeagueMessage message) {
		StringBuilder buffer = new StringBuilder();
		buffer.append(message.getName());
		for (String value : message.getValues()) {
			buffer.append(",");
			buffer.append(value);
		}

		try {
			broadcastMessage(buffer.toString());
		} catch (IOException ex) {
			throw new RuntimeException(ex);
		}
	}

	private HatLeagueMessage[] decodeHatLeagueMessages(String receivedBroadcast) {
		String[] messages = receivedBroadcast.split("\n");
		List<HatLeagueMessage> decodedMessages = new ArrayList<HatLeagueMessage>();
		for (String message : messages) {
			if (!message.equals("")) {
				String[] messageParts = message.split(",");
				// Dang it! Why did they have to wait until Java 1.6 to
				// introduce Arrays.copyOfRange()?
				String[] valueParts = new String[messageParts.length - 1];
				for (int i = 1; i < messageParts.length; i++) {
					valueParts[i - 1] = messageParts[i];
				}

				decodedMessages.add(new HatLeagueMessage(messageParts[0],
						valueParts));
			}
		}

		return decodedMessages.toArray(new HatLeagueMessage[decodedMessages.size()]);
	}

	// CLASS INTERNALS START HERE. //

	private static class TickCondition extends Condition {
		public TickCondition(String name, int priority) {
			super(name, priority);
		}

		public boolean test() {
			return true;
		}
	}

	private static final String TICK_BEGIN_NAME = "onTickBegin";
	private static final String TICK_END_NAME = "onTickStart";

	private Map<String, Double> enemyEnergies = new HashMap<String, Double>();

	@Override
	public final void run() {
		addCustomEvent(new TickCondition(TICK_BEGIN_NAME, 99));
		addCustomEvent(new TickCondition(TICK_END_NAME, 1));
		hlRun();
	}

	@Override
	public final void onCustomEvent(CustomEvent e) {
		String name = e.getCondition().getName();
		if (name.equals(TICK_BEGIN_NAME)) {
			broadcastHatLeagueMessage(new HatLeagueMessage("PD",
					Long.toString(getTime()), Double.toString(getX()),
					Double.toString(getY()),
					Double.toString(getHeadingRadians()),
					Double.toString(getVelocity()),
					Double.toString(getEnergy())));
			hlOnTickBegin();
		} else if (name.equals(TICK_END_NAME)) {
			hlOnTickEnd();
		} else {
			hlOnCustomEvent(e);
		}
	}

	@Override
	public final void onScannedRobot(ScannedRobotEvent e) {
		String name = e.getName();
		if (!isTeammate(name)) {
			long time = getTime();
			double distance = e.getDistance();
			Point2D.Double location = project(
					new Point2D.Double(getX(), getY()), e.getBearingRadians(),
					distance);
			double heading = e.getHeadingRadians();
			double velocity = e.getVelocity();
			double energy = e.getEnergy();

			HatLeagueMessage scannedMessage = new HatLeagueMessage("ES",
					Long.toString(time), Double.toString(location.getX()),
					Double.toString(location.getY()), Double.toString(heading),
					Double.toString(velocity), Double.toString(energy), name);
			passHatLeagueMessage(scannedMessage);
			broadcastHatLeagueMessage(scannedMessage);

			if (enemyEnergies.containsKey(name)) {
				double lastEnergy = enemyEnergies.get(name);
				double difference = lastEnergy - energy;
				if (difference > 0.09 && difference < 3.01) {
					HatLeagueMessage waveMessage = new HatLeagueMessage("EW",
							Long.toString(time),
							Double.toString(location.getX()),
							Double.toString(location.getY()),
							Double.toString(getBulletSpeed(difference)));
					passHatLeagueMessage(waveMessage);
					broadcastHatLeagueMessage(waveMessage);
				}
			}

			enemyEnergies.put(name, energy);
		}
	}

	@Override
	public final void onMessageReceived(MessageEvent e) {
		HatLeagueMessage[] messages = decodeHatLeagueMessages(e.getMessage().toString());
		for (HatLeagueMessage message : messages) {
			passHatLeagueMessage(message);
		}
	}

	private void passHatLeagueMessage(HatLeagueMessage message) {
		String messageType = message.getName();
		String[] messageValues = message.getValues();
		if (messageType.equals("PD")) {
			hlOnPartnerDataReceived(Long.parseLong(messageValues[0]),
					new Point2D.Double(Double.parseDouble(messageValues[1]),
							Double.parseDouble(messageValues[2])),
					Double.parseDouble(messageValues[3]),
					Double.parseDouble(messageValues[4]),
					Double.parseDouble(messageValues[5]));
		} else if (messageType.equals("ES")) {
			hlOnEnemyDataReceived(Long.parseLong(messageValues[0]),
					new Point2D.Double(Double.parseDouble(messageValues[1]),
							Double.parseDouble(messageValues[2])),
					Double.parseDouble(messageValues[3]),
					Double.parseDouble(messageValues[4]),
					Double.parseDouble(messageValues[5]), messageValues[6]);
		} else if (messageType.equals("BO")) {
			hlOnPartnerBulletOriginReceived(
					Long.parseLong(messageValues[0]),
					new Point2D.Double(Double.parseDouble(messageValues[1]),
							Double.parseDouble(messageValues[2])),
					Double.parseDouble(messageValues[3]),
					getBulletPowerFromSpeed(Double.parseDouble(messageValues[4])));
		} else if (messageType.equals("EW")) {
			hlOnEnemyWaveOriginReceived(
					Long.parseLong(messageValues[0]),
					new Point2D.Double(Double.parseDouble(messageValues[1]),
							Double.parseDouble(messageValues[2])),
					getBulletPowerFromSpeed(Double.parseDouble(messageValues[3])));
		} else if (messageType.equals("MR")) {
			hlOnPartnerRadarTargetReceived(messageValues[0]);
		} else if (messageType.equals("MB")) {
			hlOnPartnerBulletTargetReceived(messageValues[0]);
		} else if (messageType.equals("TR")) {
			hlOnSuggestedRadarTargetReceived(messageValues[0]);
		} else if (messageType.equals("TB")) {
			hlOnSuggestedBulletTargetReceived(messageValues[0]);
		} else if (messageType.equals("NA")) {
			// This is so that we correctly parse custom messages containing
			// commas.
			StringBuilder joinedMessage = new StringBuilder();
			for (int i = 0; i < messageValues.length; i++) {
				joinedMessage.append(messageValues[i]);
				if (i < messageValues.length - 1) {
					joinedMessage.append(",");
				}
			}

			hlOnCustomMessageReceived(joinedMessage.toString());
		} else {
			throw new RuntimeException("Unrecognized message type: "
					+ messageType);
		}
	}

	private static double getBulletPowerFromSpeed(double bulletSpeed) {
		return (20.0 - bulletSpeed) / 3;
	}

	private static Point2D.Double project(Point2D.Double sourceLocation,
			double angle, double distance) {
		return new Point2D.Double(
				sourceLocation.x + Math.sin(angle) * distance, sourceLocation.y
						+ Math.cos(angle) * distance);
	}
}

Example robot

package wiki.hat;

import java.awt.Color;
import java.awt.geom.*;

public class HatLeagueTestRobot extends HatLeagueRobot {
	@Override
	public void hlRun() {
		setColors(Color.RED, Color.ORANGE, Color.RED);
		setTurnRadarRightRadians(Double.POSITIVE_INFINITY);
	}

	@Override
	public void hlOnPartnerBulletOriginReceived(long time,
			Point2D.Double location, double heading, double velocity) {
		out.println(time + " - partner fired bullet: location " + location
				+ ", heading " + heading + ", velocity " + velocity);
	}

	@Override
	public void hlOnTickEnd() {
		hlSetFireBullet(0.5);
	}
}