Difference between revisions of "PluggableRobot/Source"

From Robowiki
Jump to navigation Jump to search
(Created Source Code category for pages with full source)
m (→‎ListenerDelegate.java: Fixed mistake in documentation)
Line 288: Line 288:
 
  * listener (first registered, first notified), then by the order of the listener interfaces
 
  * 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:
 
  * declared on the listener implementation. So a class with a declaration like this:
  *    public class MyClass implements ScannedRobotListener, EventListener.Death
+
  *    public class MyClass implements EventListener.ScannedRobot, EventListener.Death
 
  * ...will get notified of the ScannedRobotEvent *before* the DeathEvent!
 
  * ...will get notified of the ScannedRobotEvent *before* the DeathEvent!
 
  * @author Robert J. Walker
 
  * @author Robert J. Walker

Revision as of 05:16, 1 October 2008

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