User:AaronR/HatLeagueRobot

From Robowiki
Jump to navigation Jump to search

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);
	}
}