PluggableRobot/Source

From Robowiki
Jump to navigation Jump to search

Below is the source for PluggableRobot. Pillage to your heart's content. PluggableRobot is released under the RoboWiki Limited Public Code License.

Canvas.java

<nowiki>
/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/w/index.php?title=PluggableRobot
 * This software is made available under the RoboWiki Limited Public Code
 * License (RWLPCL). The full text of the license may be found at:
 * http://robowiki.net/w/index.php?title=RWLPCL
 */
package rjw.pluggablerobot;

import java.awt.*;
import java.awt.geom.Point2D;

import robocode.AdvancedRobot;

/**
 * A convenience wrapper around Graphics2D which provides the following
 * features:
 * 
 * - Accepts doubles for many arguments which are ints in Graphics2D
 * - getCenter(), getHeight() and getWidth()
 * - drawCircle(), fillCircle()
 * 
 * @author Robert J. Walker
 */
public class Canvas {
	private Graphics2D _g;
	private double _width;
	private double _height;
	private Point2D.Double _center;

	Canvas(AdvancedRobot bot) {
		_g = bot.getGraphics();
		_width = bot.getBattleFieldWidth();
		_height = bot.getBattleFieldHeight();
		_center = new Point2D.Double(_width / 2, _height / 2);
	}

	/**
	 * @see java awt.Graphics2D.clearRect(int, int, int, int)
	 */
	public void clearRect(double x, double y, double width, double height) {
		_g.clearRect(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.clip(java.awt.Shape)
	 */
	public void clip(Shape shape) {
		_g.clip(shape);
	}

	/**
	 * @see java awt.Graphics2D.clipRect(int, int, int, int)
	 */
	public void clipRect(double x, double y, double width, double height) {
		_g.clipRect(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.copyArea(int, int, int, int, int, int)
	 */
	public void copyArea(double x, double y, double width, double height,
			double dx, double dy) {
		_g.copyArea(i(x), i(y), i(width), i(height), i(dx), i(dy));
	}

	/**
	 * @see java awt.Graphics2D.draw(java.awt.Shape)
	 */
	public void draw(Shape shape) {
		_g.draw(shape);
	}

	/**
	 * @see java awt.Graphics2D.drawArc(int, int, int, int, int, int)
	 */
	public void drawArc(double x, double y, double width, double height,
			double startAngle, double arcAngle) {
		_g.drawArc(i(x), i(y), i(width), i(height), i(startAngle), i(arcAngle));
	}

	/**
	 * Convenience method for drawing a circle, given the center coordinates and
	 * radius.
	 */
	public void drawCircle(double x, double y, double r) {
		int d = i(r * 2);
		_g.drawOval(i(x - r), i(y - r), d, d);
	}

	/**
	 * @see java awt.Graphics2D.drawLine(int, int, int, int)
	 */
	public void drawLine(double x1, double y1, double x2, double y2) {
		_g.drawLine(i(x1), i(y1), i(x2), i(y2));
	}

	/**
	 * @see java awt.Graphics2D.drawOval(int, int, int, int)
	 */
	public void drawOval(double x, double y, double width, double height) {
		_g.drawOval(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.drawRect(int, int, int, int)
	 */
	public void drawRect(double x, double y, double width, double height) { 
		_g.drawRect(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.drawRoundRect(int, int, int, int, int, int)
	 */
	public void drawRoundRect(double x, double y, double width, double height,
			double arcWidth, double arcHeight) { 
		_g.drawRoundRect(i(x), i(y), i(width), i(height), i(arcWidth),
				i(arcHeight));
	}

	/**
	 * @see java awt.Graphics2D.drawString(String, float, float)
	 */
	public void drawString(String str, double x, double y) {
		_g.drawString(str, (float) x, (float) y);
	}

	/**
	 * @see java awt.Graphics2D.fill(java.awt.Shape)
	 */
	public void fill(Shape shape) {
		_g.fill(shape);
	}

	/**
	 * @see java awt.Graphics2D.fillArc(int, int, int, int, int, int)
	 */
	public void fillArc(double x, double y, double width, double height,
			double startAngle, double arcAngle) {
		_g.fillArc(i(x), i(y), i(width), i(height), i(startAngle), i(arcAngle));
	}

	/**
	 * Convenience method for filling a circle, given the center coordinates and
	 * radius.
	 */
	public void fillCircle(double x, double y, double r) {
		int d = i(r * 2);
		_g.fillOval(i(x - r), i(y - r), d, d);
	}

	/**
	 * @see java awt.Graphics2D.fillOval(int, int, int, int)
	 */
	public void fillOval(double x, double y, double width, double height) {
		_g.fillOval(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.fillRect(int, int, int, int)
	 */
	public void fillRect(double x, double y, double width, double height) { 
		_g.fillRect(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.fillRoundRect(int, int, int, int, int, int)
	 */
	public void fillRoundRect(double x, double y, double width, double height,
			double arcWidth, double arcHeight) { 
		_g.fillRoundRect(i(x), i(y), i(width), i(height), i(arcWidth),
				i(arcHeight));
	}

	/**
	 * @see java awt.Graphics2D.getBackground()
	 */
	public Color getBackground() {
		return _g.getBackground();
	}

	/**
	 * Returns a Point2D located at the center of the canvas.
	 */
	public Point2D.Double getCenter() {
		return _center;
	}

	/**
	 * @see java awt.Graphics2D.getClip()
	 */
	public Shape getClip() {
		return _g.getClip();
	}

	/**
	 * @see java awt.Graphics2D.getClipBounds()
	 */
	public Rectangle getClipBounds() {
		return _g.getClipBounds();
	}

	/**
	 * @see java awt.Graphics2D.getClipBounds(java.awt.Rectangle)
	 */
	public Rectangle getClipBounds(Rectangle r) {
		return _g.getClipBounds(r);
	}

	/**
	 * @see java awt.Graphics2D.getColor()
	 */
	public Color getColor() {
		return _g.getColor();
	}

	/**
	 * @see java awt.Graphics2D.getDeviceConfiguration()
	 */
	public GraphicsConfiguration getDeviceConfiguration() {
		return _g.getDeviceConfiguration();
	}

	/**
	 * Returns a reference to the actual Graphics2D object wrapped by the
	 * Canvas.
	 */
	public Graphics2D getGraphics() {
		return _g;
	}

	/**
	 * Returns the canvas's height.
	 */
	public double getHeight() {
		return _height;
	}

	/**
	 * @see java awt.Graphics2D.getPaint()
	 */
	public Paint getPaint() {
		return _g.getPaint();
	}

	/**
	 * @see java awt.Graphics2D.getStroke()
	 */
	public Stroke getStroke() {
		return _g.getStroke();
	}

	/**
	 * Returns the canvas's width.
	 */
	public double getWidth() {
		return _width;
	}

	/**
	 * @see java awt.Graphics2D.hit(java.awt.Rectangle, java.awt.Shape, boolean)
	 */
	public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
		return _g.hit(rect, s, onStroke);
	}

	/**
	 * @see java awt.Graphics2D.hitClip(int, int, int, int)
	 */
	public boolean hitClip(double x, double y, double width, double height) {
		return _g.hitClip(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.setBackground(java.awt.Color)
	 */
	public void setBackground(Color color) {
		_g.setBackground(color);
	}

	/**
	 * @see java awt.Graphics2D.setClip(int, int, int, int)
	 */
	public void setClip(double x, double y, double width, double height) {
		_g.setClip(i(x), i(y), i(width), i(height));
	}

	/**
	 * @see java awt.Graphics2D.setClip(java.awt.Shape)
	 */
	public void setClip(Shape clip) {
		_g.setClip(clip);
	}

	/**
	 * @see java awt.Graphics2D.setColor(java.awt.Color)
	 */
	public void setColor(Color c) {
		_g.setColor(c);
	}

	/**
	 * @see java awt.Graphics2D.setPaint(java.awt.Paint)
	 */
	public void setPaint(Paint paint) {
		_g.setPaint(paint);
	}

	/**
	 * @see java awt.Graphics2D.setStroke(java.awt.Stroke)
	 */
	public void setStroke(Stroke stroke) {
		_g.setStroke(stroke);
	}

	/**
	 * Rounds a double to the nearest integer and returns it as an int.
	 */
	private static int i(double d) {
		return (int) Math.round(d);
	}
}
</nowiki>

Component.java

<nowiki>
/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/w/index.php?title=PluggableRobot
 * This software is made available under the RoboWiki Limited Public Code
 * License (RWLPCL). The full text of the license may be found at:
 * http://robowiki.net/w/index.php?title=RWLPCL
 */
package rjw.pluggablerobot;

/**
 * Components encapsulate the main combat behavior of the robot into pluggable
 * modules. Any code that actually sets an action for the robot (moving,
 * turning, rotating the turret or radar, etc.) should be done in the go()
 * method. Components should NEVER call any blocking methods; PluggableRobot
 * will call execute() automatically when all components have had a chance to
 * act.
 * @author Robert J. Walker
 */
public interface Component {
	/**
	 * Asks this Component to execute any actions that it wants to take this
	 * turn. PluggableRobot will call this method once per turn for each
	 * registered Component. Don't put any blocking calls in here;
	 * PluggableRobot will call execute() for you automatically after calling
	 * go() on each Component.
	 */
	public abstract void go();
}
</nowiki>

EventListener.java

<nowiki>
/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/w/index.php?title=PluggableRobot
 * This software is made available under the RoboWiki Limited Public Code
 * License (RWLPCL). The full text of the license may be found at:
 * http://robowiki.net/w/index.php?title=RWLPCL
 */
package rjw.pluggablerobot;

import robocode.*;

/**
 * Event listener interfaces. Objects that wish to be notified of events must
 * extend one or more of these subinterfaces and register themselves with
 * PluggableRobot at the start of the round via the registerListener() method.
 * @author Robert J. Walker
 */
public interface EventListener {
	public interface BattleEnded extends EventListener {
		/**
		 * Called by the ListenerDelegate when the battle ends.
		 */
		public void notifyBattleEnded(BattleEndedEvent event);
	}

	public interface BulletHitBullet extends EventListener {
		/**
		 * Called by the ListenerDelegate when a bullet fired by your robot has
		 * hit another bullet.
		 */
		public void notifyBulletHitBullet(BulletHitBulletEvent event);
	}

	public interface BulletHit extends EventListener {
		/**
		 * Called by the ListenerDelegate when a bullet fired by your robot has
		 * hit another robot.
		 */
		public void notifyBulletHit(BulletHitEvent event);
	}

	public interface BulletMissed extends EventListener {
		/**
		 * Called by the ListenerDelegate when a bullet fired by your robot has
		 * hit a wall.
		 */
		public void notifyBulletMissed(BulletMissedEvent event);
	}

	public interface Death extends EventListener {
		/**
		 * Called by the ListenerDelegate when your robot has been destroyed.
		 */
		public void notifyDeath(DeathEvent event);
	}

	public interface HitByBullet extends EventListener {
		/**
		 * Called by the ListenerDelegate when your robot has been hit by an
		 * enemy bullet.
		 */
		public void notifyHitByBullet(HitByBulletEvent event);
	}

	public interface HitRobot extends EventListener {
		/**
		 * Called by the ListenerDelegate when your robot has collided with
		 * another robot.
		 */
		public void notifyHitRobot(HitRobotEvent event);
	}

	public interface HitWall extends EventListener {
		/**
		 * Called by the ListenerDelegate when your robot has collided with a
		 * wall.
		 */
		public void notifyHitWall(HitWallEvent event);
	}

	public interface RobotDeath extends EventListener {
		/**
		 * Called by the ListenerDelegate when an enemy robot has been
		 * destroyed.
		 */
		public void notifyRobotDeath(RobotDeathEvent event);
	}

	public interface ScannedRobot extends EventListener {
		/**
		 * Called by the ListenerDelegate when your radar has swept over an
		 * enemy robot.
		 */
		public void notifyScannedRobot(ScannedRobotEvent event);
	}

	public interface SkippedTurn extends EventListener {
		/**
		 * Called by the ListenerDelegate when your robot has skipped a turn.
		 */
		public void notifySkippedTurn(SkippedTurnEvent event);
	}

	public interface Status extends EventListener {
		/**
		 * Called by the ListenerDelegate every turn with a robot status update.
		 */
		public void notifyStatus(StatusEvent event);
	}

	public interface Win extends EventListener {
		/**
		 * Called by the ListenerDelegate when all robots besides yours have
		 * been destroyed.
		 */
		public void notifyWin(WinEvent event);
	}


	// Internal event listeners

	/**
	 * An EventListener used only by the Hud to inform it of KeyPressedEvents
	 * so that it handle requests to toggle layers.
	 */
	interface _KeyPressed extends EventListener {
		/**
		 * Notifies the Hud that a key was pressed.
		 */
		public void notifyKeyPressed(KeyPressedEvent event);
	}

	/**
	 * An EventListener used only by the ListenerDelegate to inform it of
	 * PaintEvents. The ListenerDelegate registers with itself as being
	 * interested in this event. This is the connection point for
	 * PluggableRobot's debug graphics harness.
	 */
	interface _Paint extends EventListener {
		/**
		 * Notifies the ListenerDelegate that a PaintEvent has been received.
		 */
		public void notifyPaint(PaintEvent event);
	}
}
</nowiki>

Hud.java

<nowiki>
/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/w/index.php?title=PluggableRobot
 * This software is made available under the RoboWiki Limited Public Code
 * License (RWLPCL). The full text of the license may be found at:
 * http://robowiki.net/w/index.php?title=RWLPCL
 */
package rjw.pluggablerobot;

import java.util.ArrayList;

import rjw.pluggablerobot.EventListener._KeyPressed;
import robocode.AdvancedRobot;
import robocode.KeyPressedEvent;

/**
 * The Hud is responsible for drawing debug graphics. The Hud has multiple
 * layers, each of which can be enabled or disabled independently through a key
 * binding. Any object which is interested in contributing to the Hud must
 * register itself with PluggableRobot, declaring which layer it wishes to paint
 * on. Layers are painted in the order in which they were declared, and
 * individual painters are invoked in the order that they were registered for
 * that layer.
 * 
 * Concept based on previous work by Nfwu and David Alves:
 * http://robowiki.net/w/index.php?title=User:Nfwu/Painter
 *
 * @author Robert J. Walker
 */
public class Hud implements _KeyPressed {
	private Canvas _canvas;
	private ArrayList<Layer> _layers = new ArrayList<Layer>();

	public Hud(AdvancedRobot bot) {
		_canvas = new Canvas(bot);
	}

	/**
	 * Constructs a new Layer with the given name and bound to the indicated
	 * key. The enabled argument determines whether or not the layer should
	 * be on or off by default.
	 */
	public void createLayer(int key, String name, boolean enabled) {
		Layer layer = new Layer(key, name, enabled);
		_layers.add(layer);
	}

	/**
	 * Registers a painter with the layer bound to the given key.
	 */
	public void registerPainter(int key, Painter painter) {
		for (Layer layer : _layers) {
			if (layer.getKey() == key) {
				layer.addPainter(painter);
				return;
			}
		}

		throw new IllegalArgumentException("No layer bound to that key!");
	}

	/**
	 * Notifes the Hud that a key was pressed, and toggles the corresponding
	 * layer, if it exists.
	 */
	@Override
	public void notifyKeyPressed(KeyPressedEvent event) {
		Layer layer = getLayer(event.getSourceEvent().getKeyCode());
		 
		if (layer != null) {
			layer.toggle();
		}
	}

	/**
	 * Called by PluggableRobot when it's time to paint the Hud.
	 */
	void paint(long tick) {
		for (Layer layer : _layers) {
			layer.paint(_canvas, tick);
		}
	}

	/**
	 * Returns the layer bound to the given key.
	 */
	private Layer getLayer(int key) {
		for (Layer layer : _layers) {
			if (layer.getKey() == key) {
				return layer;
			}
		}

		return null;
	}


	/**
	 * Objects which implement the Painter interface and are registered
	 * PluggableRobot will get their paint() methods called when it is time for
	 * them to paint. Note that any one object that implements Painter may only
	 * paint on one layer of the Hud.
	 */
	public interface Painter {
		/**
		 * Notifies the Painter that it's time to paint, and provides it with a
		 * Canvas object to paint with.
		 */
		public void paint(Canvas canvas, long tick);
	}

	/**
	 * A single Hud layer, responsible for painting itself by calling the
	 * Painters that which to paint on it. 
	 */
	private static class Layer {
		private int _key;
		private String _name;
		private boolean _enabled;
		private ArrayList<Painter> _painters = new ArrayList<Painter>();

		/**
		 * Constructs a new Layer with the given name and bound to the indicated
		 * key. The enabled argument determines whether or not the layer should
		 * be on or off by default.
		 */
		Layer(int key, String name, boolean enabled) {
			_key = key;
			_name = name;
			_enabled = enabled;
		}

		/**
		 * Returns the key to which this Layer is bound.
		 */
		int getKey() {
			return _key;
		}

		/**
		 * Returns this Layer's name.
		 */
		String getName() {
			return _name;
		}

		/**
		 * Adds a new Painter to this Layer.
		 */
		void addPainter(Painter painter) {
			_painters.add(painter);
		}

		/**
		 * Returns whether or not this Layer is enabled.
		 */
		boolean isEnabled() {
			return _enabled;
		}

		/**
		 * Toggles this Layer between being enabled and disabled.
		 */
		void toggle() {
			_enabled = !_enabled;
		}

		/**
		 * Notifies the Painter that it's time to paint, and provides it with a
		 * Canvas object to paint with.
		 */
		void paint(Canvas canvas, long tick) {
			if (!_enabled) {
				return;
			}

			for (Painter painter : _painters) {
				painter.paint(canvas, tick);
			}
		}
	}
}
</nowiki>

ListenerDelegate.java

<nowiki>
/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/w/index.php?title=PluggableRobot
 * This software is made available under the RoboWiki Limited Public Code
 * License (RWLPCL). The full text of the license may be found at:
 * http://robowiki.net/w/index.php?title=RWLPCL
 */
package rjw.pluggablerobot;

import java.util.*;

import robocode.*;

/**
 * Class that manages all the event listeners for a PluggableRobot and delegates
 * events to the appropriate EventListeners. Unlike the default Robocode
 * behavior, events are doled out first by listener (first registered, first
 * notified), then by the order of the listener interfaces declared on the
 * listener implementation. This allows each component to get notified of events
 * in any order it likes, regardless of event priorities or the order used by
 * other components.
 * 
 * PaintEvents are handled differently; they are captured and held until all
 * EventListeners have been notified and Components have acted, so that as much
 * information as possible is available for debug painting.
 * 
 * @author Robert J. Walker
 */
public class ListenerDelegate implements EventListener._Paint {
	private static HashMap<Class, Invoker> _invokers;

	// Build the invoker map, so we can look up invokers by listener class
	static {
		_invokers = new HashMap<Class, Invoker>();

		// interal paint event listener
		_invokers.put(_Paint.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof PaintEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((_Paint) listener).notifyPaint((PaintEvent) event);
			}
		});

		// interal key pressed event listener
		_invokers.put(_KeyPressed.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof KeyPressedEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((_KeyPressed) listener).notifyKeyPressed(
						(KeyPressedEvent) event
				);
			}
		});

		// battle ended
		_invokers.put(EventListener.BattleEnded.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof BattleEndedEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.BattleEnded) listener).notifyBattleEnded(
						(BattleEndedEvent) event
				);
			}
		});

		// bullet hit
		_invokers.put(EventListener.BulletHit.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof BulletHitEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.BulletHit) listener).notifyBulletHit(
						(BulletHitEvent) event
				);
			}
		});

		// bullet hit bullet
		_invokers.put(EventListener.BulletHitBullet.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof BulletHitBulletEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.BulletHitBullet) listener)
					.notifyBulletHitBullet((BulletHitBulletEvent) event);
			}
		});

		// bullet missed
		_invokers.put(EventListener.BulletMissed.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof BulletMissedEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.BulletMissed) listener).notifyBulletMissed(
						(BulletMissedEvent) event
				);
			}
		});

		// death
		_invokers.put(EventListener.Death.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof DeathEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.Death) listener).notifyDeath(
						(DeathEvent) event
				);
			}
		});

		// hit by bullet
		_invokers.put(EventListener.HitByBullet.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof HitByBulletEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.HitByBullet) listener).notifyHitByBullet(
						(HitByBulletEvent) event
				);
			}
		});

		// hit robot
		_invokers.put(EventListener.HitRobot.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof HitRobotEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.HitRobot) listener).notifyHitRobot(
						(HitRobotEvent) event
				);
			}
		});

		// hit wall
		_invokers.put(EventListener.HitWall.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof HitWallEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.HitWall) listener).notifyHitWall(
						(HitWallEvent) event
				);
			}
		});

		// robot death
		_invokers.put(EventListener.RobotDeath.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof RobotDeathEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.RobotDeath) listener).notifyRobotDeath(
						(RobotDeathEvent) event
				);
			}
		});

		// scanned robot
		_invokers.put(EventListener.ScannedRobot.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof ScannedRobotEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.ScannedRobot) listener).notifyScannedRobot(
						(ScannedRobotEvent) event
				);
			}
		});

		// skipped turn
		_invokers.put(EventListener.SkippedTurn.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof SkippedTurnEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.SkippedTurn) listener).notifySkippedTurn(
						(SkippedTurnEvent) event
				);
			}
		});

		// status
		_invokers.put(EventListener.Status.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof StatusEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.Status) listener).notifyStatus(
						(StatusEvent) event
				);
			}
		});

		// win
		_invokers.put(EventListener.Win.class, new Invoker() {
			protected boolean consumesEvent(Event event) {
				return event instanceof WinEvent;
			}

			protected void invoke(EventListener listener, Event event) {
				((EventListener.Win) listener).notifyWin((WinEvent) event);
			}
		});
	}


	private ArrayList<EventListener> _listeners =
			new ArrayList<EventListener>();
	private PaintEvent _lastPaintEvent = null;

	public ListenerDelegate() {
		register(this);	// register itself for the PaintEvent
	}

	/**
	 * Register a new EventListener.
	 */
	public void register(EventListener listener) {
		_listeners.add(listener);
	}

	/**
	 * Hand out event notifications to the EventListeners. 
	 */
	public void processEvents(List<Event> events) {
		// Notify listeners in the order they were registered
		for (EventListener listener : _listeners) {
			Class[] interfaces = listener.getClass().getInterfaces();

			// Iterate the interfaces on each listener
			for (Class iface : interfaces) {
				// Skip if interface does not descend from EventListener
				if (!EventListener.class.isAssignableFrom(iface)) {
					continue;
				}

				// Get the invoker for this interface
				Invoker invoker = _invokers.get(iface);

				// Find the events this invoker consumes
				for (Event event : events) {
					// Skip if listener isn't interested in this kind of event
					if (!invoker.consumesEvent(event)) {
						continue;
					}

					// Notify the listener
					invoker.invoke(listener, event);
				}
			}
		}
	}

	/**
	 * Called by a special internal EventListener to handle PaintEvents.
	 */
	@Override
	public void notifyPaint(PaintEvent event) {
		_lastPaintEvent = event;
	}

	/**
	 * Returns the most recently received PaintEvent, or null if no PaintEvent
	 * has been received.
	 */
	public PaintEvent getLastPaintEvent() {
		return _lastPaintEvent;
	}


	/**
	 * An object that knows about a Robocode Event class and how to invoke its
	 * corresponding EventListener.
	 */
	private static abstract class Invoker {
		protected abstract boolean consumesEvent(Event event);

		/**
		 * Invokes the given EventListener, passing in a Robocode Event object.
		 */
		protected abstract void invoke(EventListener listener, Event event);
	}
}
</nowiki>

Math2.java

<nowiki>
/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/w/index.php?title=PluggableRobot
 * This software is made available under the RoboWiki Limited Public Code
 * License (RWLPCL). The full text of the license may be found at:
 * http://robowiki.net/w/index.php?title=RWLPCL
 */
package rjw.pluggablerobot;

import java.awt.geom.Point2D;
import java.util.Random;

import robocode.util.Utils;

/**
 * Math utility class. Note that trig functions are reversed in order to provide
 * correct results for the Robocode coordinate system.
 * 
 * @author Robert J. Walker
 */
public final class Math2 {
	public static final double PI_OVER_2 = Math.PI / 2;

	private static final Random random = new Random();

	private Math2() {
	}

	/**
	 * Returns a random int between the min and max arguments, inclusive.
	 */
    public static int randomInteger(int min, int max) {
    	return random.nextInt(max - min   1)   min;
    }

    /**
     * If value is less than min, returns min. If value is greater than max,
     * returns max. Otherwise, returns value.
     */
    public static double limit(double min, double value, double max) {
        return Math.max(min, Math.min(value, max));
    }

    /**
     * Adds the X and Y components of the given Point2D.Double objects and
     * returns a new Point2D.Double object with the result.
     */
    public static Point2D.Double add(Point2D.Double point1,
    		Point2D.Double point2) {
		return new Point2D.Double(point1.x   point2.x, point1.y   point2.y);
	}

    /**
     * Subtracts the X and Y components of the second given Point2D.Double
     * object from those of the first and returns a new Point2D.Double object
     * with the result.
     */
	public static Point2D.Double subtract(Point2D.Double point1, Point2D.Double point2) {
		return new Point2D.Double(point1.x - point2.x, point1.y - point2.y);
	}

	/**
	 * Returns the absolute bearing in radians from the given origin point to
	 * the given target point.
	 */
	public static double getAbsoluteTargetBearing(Point2D.Double origin,
			Point2D.Double target) {
		return Utils.normalAbsoluteAngle(Math.atan2(target.x - origin.x,
				target.y - origin.y));
	}

	/**
	 * Returns a Point2D.Double object indicating the relative position of an
	 * object at the given angle (in radians) and distance from the origin.
	 */
	public static Point2D.Double getRelativePosition(double angle,
			double distance) {
		double dx = distance * Math.sin(angle);
		double dy = distance * Math.cos(angle);
		return new Point2D.Double(dx, dy);
	}

	/**
	 * Returns a Point2D.Double object indicating the position of an object at
	 * the given angle (in radians) and distance from the given origin point.
	 */
	public static Point2D.Double getAbsolutePosition(Point2D.Double origin,
			double angle, double distance) {
		double x = origin.x   distance * Math.sin(angle);
		double y = origin.y   distance * Math.cos(angle);
		return new Point2D.Double(x, y);
	}

    /**
     * Converts degrees to radians.
     */
    public static double degToRad(double degrees) {
    	return Math.toRadians(degrees);
    }

    /**
     * Converts radians to degrees.
     */
    public static double radToDeg(double radians) {
    	return Math.toDegrees(radians);
    }
}
</nowiki>

PluggableRobot.java

<nowiki>
/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/w/index.php?title=PluggableRobot
 * This software is made available under the RoboWiki Limited Public Code
 * License (RWLPCL). The full text of the license may be found at:
 * http://robowiki.net/w/index.php?title=RWLPCL
 */
package rjw.pluggablerobot;

import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;

import rjw.pluggablerobot.Hud.Painter;
import robocode.*;

/**
 * An abstract robot class that provides a pluggable architecture, sophisticated
 * event management and advanced debug graphics handling.
 * 
 * @author Robert Walker
 */
public abstract class PluggableRobot extends AdvancedRobot {
	private static boolean _battleInitialized = false;
	private static Point2D.Double _center;

	private ListenerDelegate _listenerDelegate;
	private ArrayList<Component> _components = new ArrayList<Component>();
	private Hud _hud;

	public PluggableRobot() {
		_listenerDelegate = new ListenerDelegate();
	}

	/**
	 * PluggableRobot's main execution loop. Rather that implementing run() in
	 * your own robot, you will register listeners, components and painters in
	 * the initRound() method, and those objects will handle your robot's combat
	 * logic. This allows you to more easily separate concerns into separate
	 * objects, and replace parts to change robot behavior.  
	 */
	@Override
	public final void run() {
		// Initialize battle (at start of first round only)
		if (!_battleInitialized) {
			_center = new Point2D.Double(getWidth() / 2, getHeight() / 2);
			initBattle();
			_battleInitialized = true;
		}

		// Mechanism for capturing all events and handing them to the
		// ListenerDelegate. 
		addCustomEvent(new Condition("eventManager") {
			public boolean test() {
				PluggableRobot.this.handleEvents();
				return false;
			}
		});

		// Initialize the Hud
		_hud = new Hud(this);
		registerListener(_hud);

		// Allow robot to initialize its parts
		initRound();

		// Main loop
		while (true) {
			// Let each component act
			for (Component component : _components) {
				component.go();
			}

			// Handle painting
			PaintEvent pe = _listenerDelegate.getLastPaintEvent();
	
			if (pe != null && pe.getTime() == getTime()) {
				_hud.paint(getTime());
			}

			execute();
		}
	}

	/**
	 * Returns a Point2D object located at the exact center of the battlefield.
	 */
	public Point2D.Double getCenter() {
		return _center;
	}

	/**
	 * Called by PluggableRobot at the start of a battle. Your implementation of
	 * this method should perform whatever operations you wish to handle at the
	 * start of a battle, such as setting tank colors and initializing memory
	 * objects that will persist between rounds.
	 */
	protected abstract void initBattle();

	/**
	 * Called by PluggableRobot at the start of each round. Your implementation
	 * of this method should create Hud layers, register your robot's
	 * EventListeners, Components and Painters, and perform any other operations
	 * you wish to handle at the start of a round.
	 */
	protected abstract void initRound();

	/**
	 * Registers an EventListener. Registration lasts until the end of the round
	 * and enables the listener to receive event notifications.
	 */
	protected final void registerListener(EventListener listener) {
		_listenerDelegate.register(listener);
	}

	/**
	 * Registers a Component. Registration lasts until the end of the round and
	 * enables the Component to be notified when it is time to act.
	 */
	protected void registerComponent(Component component) {
		_components.add(component);
	}

	/**
	 * Constructs a new Hud layer with the given name and bound to the indicated
	 * key. The enabled argument determines whether or not the layer should
	 * be on or off by default.
	 */
	protected void createLayer(int key, String name, boolean enabled) {
		_hud.createLayer(key, name, enabled);
	}

	/**
	 * Registers a Painter and binds it to the layer corresponding to the given
	 * key. Registration lasts until the end of the round and enables the
	 * Painter to draw on a Hud layer when it's time to paint.
	 */
	protected void registerPainter(int key, Painter painter) {
		_hud.registerPainter(key, painter);
	}

	/**
	 * Called by a custom event registered by PluggableRobot. This method hands
	 * the event list to the ListenerDelegate, which will in turn notify the
	 * EventListeners in the desired order.
	 */
	private void handleEvents() {
		_listenerDelegate.processEvents(getAllEvents());
		clearAllEvents();
	}

	/*
	 * Since we've got our own event handling mechanism, we don't want to use
	 * the existing one, so make these methods final.
	 */
	@Override
	public final void onBattleEnded(BattleEndedEvent event) {
	}

	@Override
	public final void onBulletHit(BulletHitEvent event) {
	}

	@Override
	public final void onBulletHitBullet(BulletHitBulletEvent event) {
	}

	@Override
	public final void onBulletMissed(BulletMissedEvent event) {
	}

	@Override
	public final void onCustomEvent(CustomEvent event) {
	}

	@Override
	public final void onDeath(DeathEvent event) {
	}

	@Override
	public final void onHitByBullet(HitByBulletEvent event) {
	}

	@Override
	public final void onHitRobot(HitRobotEvent event) {
	}

	@Override
	public final void onHitWall(HitWallEvent event) {
	}

	@Override
	public final void onKeyPressed(KeyEvent e) {
	}

	@Override
	public final void onKeyReleased(KeyEvent e) {
	}

	@Override
	public final void onKeyTyped(KeyEvent e) {
	}

	@Override
	public final void onMouseClicked(MouseEvent e) {
	}

	@Override
	public final void onMouseDragged(MouseEvent e) {
	}

	@Override
	public final void onMouseEntered(MouseEvent e) {
	}

	@Override
	public final void onMouseExited(MouseEvent e) {
	}

	@Override
	public final void onMouseMoved(MouseEvent e) {
	}

	@Override
	public final void onMousePressed(MouseEvent e) {
	}

	@Override
	public final void onMouseReleased(MouseEvent e) {
	}

	@Override
	public final void onMouseWheelMoved(MouseWheelEvent e) {
	}

	@Override
	public final void onPaint(Graphics2D g) {
	}

	@Override
	public final void onRobotDeath(RobotDeathEvent event) {
	}

	@Override
	public final void onScannedRobot(ScannedRobotEvent event) {
	}

	@Override
	public final void onSkippedTurn(SkippedTurnEvent event) {
	}

	@Override
	public final void onStatus(StatusEvent e) {
	}

	@Override
	public final void onWin(WinEvent event) {
	}
}
</nowiki>