PluggableRobot/Source

From Robowiki
< PluggableRobot
Revision as of 04:16, 1 October 2008 by RobertWalker (talk | contribs) (→‎ListenerDelegate.java: Fixed mistake in documentation)
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.

Component.java

/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/cgi-bin/robowiki?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/cgi-bin/robowiki?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();
}

EventListener.java

/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/cgi-bin/robowiki?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/cgi-bin/robowiki?RWLPCL.
 */
package rjw.pluggablerobot;

import robocode.*;

/**
 * Event listener interfaces. Objects that wish to be notified of events must extend one of these
 * subinterfaces.
 * @author Robert J. Walker
 */
public interface EventListener {
	public interface BulletHitBullet extends EventListener {
		/**
		 * Called by PluggableRobot when a bullet fired by your robot has hit another bullet.
		 */
		public void notifyBulletHitBullet(BulletHitBulletEvent event);
	}

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

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

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

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

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

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

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

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

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

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

Hud.java

/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/cgi-bin/robowiki?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/cgi-bin/robowiki?RWLPCL.
 */
package rjw.pluggablerobot;


import java.awt.Graphics2D;
import java.awt.Paint;

import robocode.Robot;

/**
 * Facilitates drawing graphics on the arena by automatically converting between values used in
 * calculations to values used for drawing. The graphics context (Graphics2D) is injected into the
 * Hud just before it is handed to Painters, then removed afterward.
 * @author Robert J. Walker
 */
public class Hud {
	/**
	 * Objects which implement this interface can be passed to PluggableRobot.registerPainter(), and
	 * will get their paint() methods called when it's time to draw the HUD.
	 */
	public interface Painter {
		/**
		 * This method will be called by PluggableRobot when it's time for this object to draw.
		 */
		public void paint(Hud hud, long tick);
	}

	private Graphics2D _g;

	public double _w;
	public double _h;

	/**
	 * Creates a new Hud object.
	 */
	public Hud(Robot bot) {
		_w = bot.getBattleFieldWidth();
		_h = bot.getBattleFieldHeight();
	}

	/**
	 * Draws a point at the given coordinates.
	 */
	public void drawPoint(double x, double y) {
		int x0 = i(x);
		int y0 = i(y);
		_g.drawLine(x0, y0, x0, y0);
	}

	/**
	 * Draws a line between the two indicated points.
	 */
	public void drawLine(double x1, double y1, double x2, double y2) {
		_g.drawLine(i(x1), i(y1), i(x2), i(y2));
	}

	/**
	 * Draws a circle with the given center point 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);
	}

	public void drawArc(double x, double y, double r, double start, double extend) {
		int d = i(r * 2);
		_g.drawArc(i(x - r), i(y - r), d, d, i(Math2.radToDeg(start)), i(Math2.radToDeg(extend)));
	}

	/**
	 * Draws a rectangle with the given position and dimensions.
	 */
	public void drawRect(double x, double y, double w, double h) {
		_g.drawRect(i(x), i(y), i(w), i(h));
	}

	/**
	 * Draws a filled rectangle with the given position and dimensions.
	 */
	public void drawFilledRect(double x, double y, double w, double h) {
		_g.fillRect(i(x), i(y), i(w), i(h));
	}

	/**
	 * Draws a left-aligned String at the given position.
	 */
	public void drawString(String s, double x, double y) {
		_g.drawString(s, i(x), i(y));
	}

	/**
	 * Draws a right-aligned String at the given position.
	 */
	public void drawStringRight(String s, double x, double y) {
		_g.drawString(s, i(x) - _g.getFontMetrics().stringWidth(s), i(y));
	}

	/**
	 * Sets the Hud's current drawing paint.
	 */
	public void setPaint(Paint c) {
		_g.setPaint(c);
	}

	/**
	 * Returns the width of the battlefield. This is useful in situations where you don't have
	 * access to the robot object.
	 */
	public double getWidth() {
		return _w;
	}

	/**
	 * Returns the height of the battlefield. This is useful in situations where you don't have
	 * access to the robot object.
	 */
	public double getHeight() {
		return _h;
	}

	/**
	 * Allows PluggableRobot to inject the graphics context into the Hud.
	 */
	public void setContext(Graphics2D g) {
		_g = g;
	}

	/**
	 * Rounds the given double value to the nearest int.
	 */
	private int i(double val) {
		return (int) Math.round(val);
	}
}

ListenerDelegate.java

/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/cgi-bin/robowiki?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/cgi-bin/robowiki?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. So a class with a declaration like this:
 *     public class MyClass implements EventListener.ScannedRobot, EventListener.Death
 * ...will get notified of the ScannedRobotEvent *before* the DeathEvent!
 * @author Robert J. Walker
 */
public class ListenerDelegate {
	private ArrayList<EventListener> _listeners = new ArrayList<EventListener>();
	private static HashMap<Class<? extends EventListener>, ListenerInvoker> _invokers;

	// Build the invoker map, allowing us to look up invokers by listener class
	static {
		_invokers = new HashMap<Class<? extends EventListener>, ListenerInvoker>();
		_invokers.put(EventListener.Death.class,
				new ListenerInvoker<EventListener.Death, DeathEvent>() {
					protected Class<DeathEvent> eventClass() { return DeathEvent.class; }
					protected void invokeListener(EventListener.Death listener, DeathEvent event) {
						listener.notifyDeath(event);
					}
				}
		);
		_invokers.put(EventListener.Win.class,
				new ListenerInvoker<EventListener.Win, WinEvent>() {
					protected Class<WinEvent> eventClass() { return WinEvent.class; }
					protected void invokeListener(EventListener.Win listener, WinEvent event) {
						listener.notifyWin(event);
					}
				}
		);
		_invokers.put(EventListener.SkippedTurn.class,
				new ListenerInvoker<EventListener.SkippedTurn, SkippedTurnEvent>() {
					protected Class<SkippedTurnEvent> eventClass() { return SkippedTurnEvent.class; }
					protected void invokeListener(EventListener.SkippedTurn listener, SkippedTurnEvent event) {
						listener.notifySkippedTurn(event);
					}
				}
		);
		_invokers.put(EventListener.ScannedRobot.class,
				new ListenerInvoker<EventListener.ScannedRobot, ScannedRobotEvent>() {
					protected Class<ScannedRobotEvent> eventClass() { return ScannedRobotEvent.class; }
					protected void invokeListener(EventListener.ScannedRobot listener, ScannedRobotEvent event) {
						listener.notifyScannedRobot(event);
					}
				}
		);
		_invokers.put(EventListener.HitByBullet.class,
				new ListenerInvoker<EventListener.HitByBullet, HitByBulletEvent>() {
					protected Class<HitByBulletEvent> eventClass() { return HitByBulletEvent.class; }
					protected void invokeListener(EventListener.HitByBullet listener, HitByBulletEvent event) {
						listener.notifyHitByBullet(event);
					}
				}
		);
		_invokers.put(EventListener.BulletHit.class,
				new ListenerInvoker<EventListener.BulletHit, BulletHitEvent>() {
					protected Class<BulletHitEvent> eventClass() { return BulletHitEvent.class; }
					protected void invokeListener(EventListener.BulletHit listener, BulletHitEvent event) {
						listener.notifyBulletHit(event);
					}
				}
		);
		_invokers.put(EventListener.BulletHitBullet.class,
				new ListenerInvoker<EventListener.BulletHitBullet, BulletHitBulletEvent>() {
					protected Class<BulletHitBulletEvent> eventClass() { return BulletHitBulletEvent.class; }
					protected void invokeListener(EventListener.BulletHitBullet listener, BulletHitBulletEvent event) {
						listener.notifyBulletHitBullet(event);
					}
				}
		);
		_invokers.put(EventListener.BulletMissed.class,
				new ListenerInvoker<EventListener.BulletMissed, BulletMissedEvent>() {
					protected Class<BulletMissedEvent> eventClass() { return BulletMissedEvent.class; }
					protected void invokeListener(EventListener.BulletMissed listener, BulletMissedEvent event) {
						listener.notifyBulletMissed(event);
					}
				}
		);
		_invokers.put(EventListener.HitRobot.class,
				new ListenerInvoker<EventListener.HitRobot, HitRobotEvent>() {
					protected Class<HitRobotEvent> eventClass() { return HitRobotEvent.class; }
					protected void invokeListener(EventListener.HitRobot listener, HitRobotEvent event) {
						listener.notifyHitRobot(event);
					}
				}
		);
		_invokers.put(EventListener.HitWall.class,
				new ListenerInvoker<EventListener.HitWall, HitWallEvent>() {
					protected Class<HitWallEvent> eventClass() { return HitWallEvent.class; }
					protected void invokeListener(EventListener.HitWall listener, HitWallEvent event) {
						listener.notifyHitWall(event);
					}
				}
		);
		_invokers.put(EventListener.RobotDeath.class,
				new ListenerInvoker<EventListener.RobotDeath, RobotDeathEvent>() {
					protected Class<RobotDeathEvent> eventClass() { return RobotDeathEvent.class; }
					protected void invokeListener(EventListener.RobotDeath listener, RobotDeathEvent event) {
						listener.notifyRobotDeath(event);
					}
				}
		);
	}

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

	/**
	 * Hand out event notifications to the EventListeners. 
	 */
	public void processEvents(Vector<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 that descend from EventListener
			for(Class iface : interfaces) {
				if(!EventListener.class.isAssignableFrom(iface)) continue;

				// Get the invoker and the corresponding event class for this interface
				ListenerInvoker invoker = _invokers.get(iface);
				Class<? extends Event> eventClass = invoker.eventClass();

				// Iterate the events and hand the ones of the proper type to the invoker
				for(Event event : events) {
					if(!eventClass.isAssignableFrom(event.getClass())) continue;
					invoker.invokeListener(listener, event);
				}
			}
		}
	}

	
	/**
	 * An object that knows about a Robocode Event class and how to invoke its corresponding
	 * EventListener.
	 * @author Robert J. Walker
	 */
	private static abstract class ListenerInvoker<K extends EventListener, V extends Event> {
		/**
		 * Returns the Robocode Event class handled by this ListenerInvoker.
		 */
		protected abstract Class<V> eventClass();

		/**
		 * Invokes the given EventListener, passing in a Robocode Event object.
		 */
		protected abstract void invokeListener(K listener, V event);
	}
}

Math2.java

/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/cgi-bin/robowiki?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/cgi-bin/robowiki?RWLPCL.
 */
package rjw.pluggablerobot;

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

import robocode.util.Utils;

/**
 * Math utility class.
 * @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;
    }

    /**
     * Returns a random double value between 0.0 (inclusive) and 1.0 (exclusive).
     */
    public static double randomDouble() {
    	return random.nextDouble();
    }

    /**
     * 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 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 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 degrees * Math.PI / 180;
    }

    /**
     * Converts radians to degrees.
     */
    public static double radToDeg(double radians) {
    	return radians * 180 / Math.PI;
    }
}

PluggableRobot.java

/*
 * PluggableRobot, by Robert J. Walker
 * Home page: http://robowiki.net/cgi-bin/robowiki?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/cgi-bin/robowiki?RWLPCL.
 */
package rjw.pluggablerobot;

import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;

import robocode.*;

/**
 * A pluggable listener and strategy architecture for a robot.
 * http://robowiki.net/cgi-bin/robowiki?PluggableRobot
 * @author Robert J. Walker
 */
public abstract class PluggableRobot extends AdvancedRobot {
	private static boolean _battleInitialized = false;
	private static Hud _hud;

	private ListenerDelegate _listenerDelegate;
	private ArrayList<Component> _components;
	private ArrayList<Hud.Painter> _painters;
	private Point2D.Double _center;

	/**
	 * Sets up the ListenerDelegate and the Component and Painter lists.
	 */
	protected PluggableRobot() {
		_listenerDelegate = new ListenerDelegate();
		_components = new ArrayList<Component>();
		_painters = new ArrayList<Hud.Painter>();
	}

	/**
	 * Set up the robot, then continuously collect events and invoke components.
	 */
	@Override
	public final void run() {
		// Initialize battle (at start of first round only)
		if(!_battleInitialized) {
			_hud = new Hud(this);
			initializeBattle();
			_battleInitialized = true;
		}

		// Register custom event test() hook for event manager
		addCustomEvent(new Condition("eventManager") {
			public boolean test() {
				PluggableRobot.this.handleEvents();
				return false;
			}
		});

		// Round is starting
		initializeRound();

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

			execute();
		}
	}

	/**
	 * This method will be called at the beginning of a battle. Robots can override this method to
	 * initialize their properties. This is a good place to initialize static properties or set your
	 * tank color.
	 */
	protected void initializeBattle() {
		// Default implementation does nothing
	}

	/**
	 * This method will be called at the beginning of each round. Robots can override this method to
	 * initialize their properties. This is a good place to set up non-static properties and
	 * register listeners, painters and components.
	 */
	protected void initializeRound() {
		// Default implementation does nothing
	}

	/**
	 * Called before events get processed each tick. The default implementation does nothing.
	 */
	public void onBeforeEventsProcessed() {
		// Do nothing
	}

	/**
	 * Returns a Point2D.Double object representing the center of the battlefield.
	 */
	public Point2D.Double getCenter() {
		if(_center == null) {
			_center = new Point2D.Double(getBattleFieldWidth() / 2, getBattleFieldHeight() / 2); 
		}

		return _center;
	}


	/**
	 * Registers the given EventListener, which will cause it to receive notifications of the events
	 * indicated by the listener interfaces it implements.
	 */
	protected void registerListener(EventListener listener) {
		_listenerDelegate.register(listener);
	}

	/**
	 * Reigsters the given Component, which will give it the opportunity to act each turn.
	 */
	protected void registerComponent(Component component) {
		_components.add(component);
	}

	/**
	 * Reigsters the given Painter, which will give it the opportunity to draw on the HUD each turn.
	 */
	protected void registerPainter(Hud.Painter painter) {
		_painters.add(painter);
	}

	/**
	 * Hand out notifications to the Painters.
	 */
	@Override
	public final void onPaint(Graphics2D g) {
		_hud.setContext(g); // Inject the graphics context into the Hud

		for(Hud.Painter painter : _painters) {
			painter.paint(_hud, getTime());
		}

		_hud.setContext(null); // Clear the injected graphics context
	}

	/**
	 * Process all the events in the queue.
	 */
	private void handleEvents() {
		onBeforeEventsProcessed();
		_listenerDelegate.processEvents(getAllEvents());
		clearAllEvents();
	}

	// Since we have our own event manager, we want to prevent overrides of the Robocode event
	// methods, so we'll make them final.

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

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

	@Override
	public final void onSkippedTurn(SkippedTurnEvent 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 onHitByBullet(HitByBulletEvent event) {
	}

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

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

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

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

	@Override
	public final void onWin(WinEvent event) {
	}
}