/**
* Specifies an EnemyRobot
*/
package epgy.util;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.util.*;
import robocode.*;
import robocode.util.Utils;
/**
* This class specifies an enemy robot. It stores robot states and
* contains information about an enemy robot
* @author Matthew Chun-Lum
*
*/
public class EPGYEnemyRobot {
public static final int MAX_HISTORY_SIZE = 100;
public static final int BOT_RADIUS = 18;
public static final Stroke absoluteDangerStroke = new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 4, 10 }, 0);
public static final Stroke desiredMinStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 4, 20 }, 0);
public static final Stroke desiredMaxStroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] { 4, 20 }, 0);
protected String name;
protected int shotsFired;
protected int shotsHit;
protected double minSafeDistance;
protected double preferredSafeDistance;
protected ArrayList<EPGYEnemyListener> listeners;
protected ArrayList<EPGYRobotState> states;
protected ArrayList<Double> trackedBearings;
protected ArrayList<Integer> trackedDirections;
protected Ellipse2D.Double absoluteDangerZone;
protected Ellipse2D.Double desiredMinDistance;
protected Ellipse2D.Double desiredMaxDistance;
protected EPGYBot reference;
/**
* @param name the name of the enemy robot
*/
public EPGYEnemyRobot(String name, EPGYBot reference) {
this.reference = reference;
this.name = name;
listeners = new ArrayList<EPGYEnemyListener>();
states = new ArrayList<EPGYRobotState>();
resetState();
shotsFired = 0;
shotsHit = 0;
}
/**
* @return the name of the enemy robot
*/
public String getName() {
return name;
}
/**
* @return {@code true} if a shot was fired
*/
public boolean shotFired() {
EPGYRobotState curr = getCurrentState();
EPGYRobotState last = getLastState();
if(last != null) {
double diff = last.energy - curr.energy;
return diff > 0 && diff <= 3.0 ;
}
return false;
}
/**
* @return the power of the shot fired (0 if no shot was fired)
*/
public double getShotPower() {
if(!shotFired())
return 0;
return getLastState().energy - getCurrentState().energy;
}
/**
* @return the number of shots fired
*/
public int getShotsFired() {
return shotsFired;
}
/**
* @return the number of shots that hit their target
*/
public int getShotsHit() {
return shotsHit;
}
/**
* increments the shotsHit var
*/
public void noteShotHit() {
shotsHit++;
}
/**
* @return the minimum safe distance to this robot
*/
public double getMinSafeDistance() {
return minSafeDistance;
}
public double getPreferredSafeDistance() {
return preferredSafeDistance;
}
/**
* Given a ScannedRobotEvent, adds a state to the list of states
* @param e
*/
public void addState(ScannedRobotEvent e) {
states.add(0, new EPGYRobotState(reference, this, e));
if(states.size() >= MAX_HISTORY_SIZE)
states.remove(states.size() - 1); // remove the last state
}
/**
* @return the current state of the enemy
*/
public EPGYRobotState getCurrentState() {
if(states.size() > 0)
return states.get(0);
return null;
}
/**
* @return the last state of the enemy or {@code null} if there exists
* only one state
*/
public EPGYRobotState getLastState() {
if(states.size() > 1)
return states.get(1);
return null;
}
/**
* @param n
* @return returns up to the last n states
*/
public ArrayList<EPGYRobotState> getLastNStates(int n) {
if(states.isEmpty())
return null;
return new ArrayList<EPGYRobotState>(states.subList(0, Math.min(n-1, states.size())));
}
/**
* @returns all of the states
*/
public ArrayList<EPGYRobotState> getStates() {
return states;
}
/**
* Clears the states in the list
*/
public void clearStates() {
states.clear();
}
/**
* @return the last direction of a RampantRobot that the enemy
* could have used to shoot
*/
public int getLastUsableSurfDirection() {
if(trackedDirections.size() > 2)
return trackedDirections.get(2);
return 0;
}
/**
* @return the last bearing of a RampantRobot that the enemy
* could have used to shoot
*/
public double getLastUsableBearing() {
if(trackedBearings.size() > 2)
return trackedBearings.get(2);
return 0;
}
/**
* Invoked at the beginning of each round.
*/
public void resetState() {
//clearStates();
trackedBearings = new ArrayList<Double>();
trackedDirections = new ArrayList<Integer>();
}
/**
* Invoked when the enemy is scanned
* Do not call manually
* @param e
*/
public void update(ScannedRobotEvent e) {
addState(e);
updateTracking(e);
if(shotFired()) {
notifyShotFired();
shotsFired++;
}
updateZones();
notifyListeners();
}
public void draw(Graphics2D g) {
Stroke stroke = g.getStroke();
g.setColor(Color.red);
g.setStroke(absoluteDangerStroke);
g.draw(absoluteDangerZone);
g.setColor(Color.yellow);
g.setStroke(desiredMinStroke);
g.draw(desiredMinDistance);
g.setColor(Color.white);
g.setStroke(desiredMaxStroke);
g.draw(desiredMaxDistance);
g.setStroke(stroke);
EPGYRobotState state = getCurrentState();
if(state == null)
return;
g.setColor(Color.white);
g.draw(new EPGYRectangle(state.location));
}
// ---------- Private ---------- //
private void updateZones() {
EPGYRobotState state = getCurrentState();
if(state == null)
return;
EPGYPoint location = state.location;
double absBearing = Utils.normalAbsoluteAngle(state.absoluteBearing + Math.PI);
double maxEscapeAngle = EPGYUtil.computeMaxEscapeAngle(EPGYUtil.computeBulletVelocity(0.1));
minSafeDistance = 30 / Math.sin(maxEscapeAngle);
preferredSafeDistance = 150;
absoluteDangerZone = new Ellipse2D.Double(location.x - minSafeDistance,
location.y - minSafeDistance,
minSafeDistance * 2,
minSafeDistance * 2);
desiredMinDistance = new Ellipse2D.Double(location.x - preferredSafeDistance,
location.y - preferredSafeDistance,
preferredSafeDistance * 2,
preferredSafeDistance * 2);
desiredMaxDistance = new Ellipse2D.Double(location.x - 400,
location.y - 400,
400 * 2,
400 * 2);
}
/*
* Updates the tracking arrays
*/
private void updateTracking(ScannedRobotEvent e) {
double lateralVelocity = reference.getVelocity() * Math.sin(e.getBearingRadians());
int direction = lateralVelocity >= 0 ? 1 : -1;
trackedDirections.add(0, direction);
trackedBearings.add(0, getCurrentState().absoluteBearing + Math.PI);
}
// ---------- Listener Code ----------- //
/**
* @param listener
* @return {@code true} if the listener was added to the list
*/
public boolean addListener(EPGYEnemyListener listener) {
if(listeners.contains(listener))
return false;
return listeners.add(listener);
}
/**
* @param listener
* @return {@code true} if the listener was removed from the list
*/
public boolean removeListener(EPGYEnemyListener listener) {
return listeners.remove(listener);
}
/**
* Removes all listeners
*/
public void removeAllListeners() {
listeners.clear();
}
/**
* Notifies all listeners
*/
public void notifyListeners() {
for(EPGYEnemyListener listener : listeners)
listener.enemyUpdated(this);
}
/**
* Notifies listeners of a shot fired
*/
public void notifyShotFired() {
for(EPGYEnemyListener listener : listeners)
listener.shotFired(this);
}
}