/**
* Util.java
*/
package rampancy.util;
import java.awt.Graphics2D;
import java.util.List;
import rampancy.RampantRobot;
import rampancy.util.movement.MovSim;
import rampancy.util.movement.MovSimStat;
import rampancy.util.wave.REnemyWave;
import robocode.AdvancedRobot;
import robocode.util.Utils;
/**
* This class contains utility methods used by Durandal
* @author Matthew Chun-Lum
*
*/
public abstract class RUtil {
public static final double WALL_STICK = 150;
public static double computeRequiredBulletPower(RPoint source, RPoint target, int time) {
double distance = source.distance(target);
double requiredVelocity = distance / (double) time;
return computeBulletPower(requiredVelocity);
}
/**
* Computes the absolute bearing from the source to the target
* @param source
* @param target
* @return the absolute bearing in radians
*/
public static double computeAbsoluteBearing(RPoint source, RPoint target) {
return Utils.normalAbsoluteAngle(Math.atan2(target.x - source.x, target.y - source.y));
}
/**
* Computes the proper guess factor for the statistics tracking array
*/
public static int computeBin(double factor, int numBins) {
double value = (factor * ((numBins - 1) / 2)) + ((numBins - 1) / 2);
return (int) limit(0, value, numBins -1);
}
/**
* Determines the power of a bullet given its velocity
* @param velocity
* @return the power of the bullet
*/
public static double computeBulletPower(double velocity) {
return Math.max(0.1, (20.0 - velocity) / 3.0);
}
/**
* Determines the velocity of a bullet given its power
* @param bulletPower
* @return the velocity of the bullet
*/
public static double computeBulletVelocity(double bulletPower) {
return (20.0 - (3.0 * bulletPower));
}
/**
* Computes the probability density function using a kernel density approximation
* @param distances
* @param h
* @return
*/
public static double computeDensity(List<Double> distances, double h) {
double factor = 1.0 / (distances.size() * h);
double k_factor = 1.0 / Math.sqrt(Math.PI * 2);
double sigma = 0;
for(int i = 0; i < distances.size(); i++)
sigma += Math.exp(-0.5 * Math.pow(distances.get(i) / h, 2));
return sigma * factor * k_factor;
}
/**
* Determines the max escape angle given velocity
* @param velocity
* @return the max escape angle
*/
public static double computeMaxEscapeAngle(double velocity) {
return Math.asin(8.0 / velocity);
}
public static double computePreciceMaxEscapeAngle(double bulletVelocity, RampantRobot reference, RPoint origin, int direction) {
RRobotState state = reference.getCurrentState();
double maxAngle = 0;
double distance = origin.distance(state.location);
double maxTicks = distance / bulletVelocity;
// project perpendicular
double goAngle = computeAbsoluteBearing(origin, state.location);
goAngle = wallSmoothing(state.location, goAngle + (Math.PI / 2.0) * direction,
direction, 1000);
MovSimStat stat = predictPosition(state.location, state.heading, goAngle, state.velocity, MovSim.defaultMaxVelocity, direction);
for(int i = 1; i < maxTicks + 1; i++) {
RPoint newLoc = new RPoint(stat.x, stat.y);
distance = origin.distance(newLoc);
goAngle = computeAbsoluteBearing(origin, newLoc);
goAngle = wallSmoothing(state.location, goAngle + (Math.PI / 2.0) * direction,
direction, 1000);
stat = predictPosition(new RPoint(stat.x, stat.y), stat.h, goAngle, stat.v, MovSim.defaultMaxVelocity, direction);
// if(distance / bulletVelocity > i + 3)
// break;
}
RPoint endPoint = new RPoint(stat.x, stat.y);
double absBearingToEnd = computeAbsoluteBearing(origin, endPoint);
double escapeAngle = absBearingToEnd - state.absoluteBearing;
return Math.abs(Utils.normalRelativeAngle(escapeAngle));
}
public static double computePreciceMaxEscapeAngle(double bulletVelocity, RampantRobot reference, REnemyRobot enemy, int direction) {
RPoint origin = reference.getCopyOfLocation();
RRobotState enemyState = enemy.getCurrentState();
double maxAngle = 0;
double distance = origin.distance(enemyState.location);
double maxTicks = distance / bulletVelocity;
// project perpendicular
double goAngle = computeAbsoluteBearing(origin, enemyState.location);
goAngle = wallSmoothing(enemyState.location, goAngle + (Math.PI / 2.0) * direction,
direction, 1000);
MovSimStat stat = predictPosition(enemyState.location, enemyState.heading, goAngle, enemyState.velocity, MovSim.defaultMaxVelocity, direction);
for(int i = 1; i < maxTicks + 1; i++) {
RPoint newLoc = new RPoint(stat.x, stat.y);
distance = origin.distance(newLoc);
goAngle = computeAbsoluteBearing(origin, newLoc);
goAngle = wallSmoothing(enemyState.location, goAngle + (Math.PI / 2.0) * direction,
direction, 1000);
stat = predictPosition(new RPoint(stat.x, stat.y), stat.h, goAngle, stat.v, MovSim.defaultMaxVelocity, direction);
// if(distance / bulletVelocity > i + 3)
// break;
}
RPoint endPoint = new RPoint(stat.x, stat.y);
double absBearingToEnd = computeAbsoluteBearing(origin, endPoint);
double escapeAngle = absBearingToEnd - enemyState.absoluteBearing;
return Math.abs(Utils.normalRelativeAngle(escapeAngle));
}
private static MovSimStat predictPosition(RPoint fromLocation, double currentHeading, double desiredHeading, double currentVelocity, double maxVelocity, double direction) {
double angleToTurn = desiredHeading - currentHeading;
int moveDirection = 1;
if(Math.cos(angleToTurn) < 0) {
angleToTurn += Math.PI;
moveDirection = -1;
}
angleToTurn = Utils.normalRelativeAngle(angleToTurn);
RBattlefield bf = RampantRobot.getGlobalBattlefield();
MovSimStat[] nextTick = MovSim.futurePos(1, fromLocation.x, fromLocation.y, currentVelocity, maxVelocity,
currentHeading, 1000 * moveDirection, angleToTurn, MovSim.defaultMaxTurnRate, bf.width, bf.height);
return nextTick[0];
}
/**
* @param location
* @param wave
* @param attackAngle
* @param direction
* @return the appropriate orbit angle
*/
public static double computeOrbitAngle(RPoint location, REnemyWave wave, double attackAngle, int direction) {
double goAngle = RUtil.computeAbsoluteBearing(wave.getOrigin(), location);
goAngle = RUtil.wallSmoothing(location, goAngle + (direction * (Math.PI / 2 + attackAngle)), direction, wave.getOrigin().distance(location));
return goAngle;
}
public static double computeOrbitAngle(RPoint location, REnemyRobot enemy, double attackAngle, int direction) {
RRobotState state = enemy.getCurrentState();
double goAngle = RUtil.computeAbsoluteBearing(state.location, location);
return RUtil.wallSmoothing(state.location, goAngle + (Math.PI / 2.0 + attackAngle) * direction, direction, state.distance);
}
/**
* Draws an oval using the passed point and radius
* @param point
* @param radius
* @param g
*/
public static void drawOval(RPoint point, int radius, Graphics2D g) {
g.drawOval((int) point.x - radius, (int) point.y - radius, radius * 2, radius * 2);
}
/**
* Fills an oval using the passed point and radius
* @param point
* @param radius
* @param g
*/
public static void fillOval(RPoint point, int radius, Graphics2D g) {
g.fillOval((int) point.x - radius, (int) point.y - radius, radius * 2, radius * 2);
}
/**
* @param point
* @return the Point's distance from the wall
*/
public static double getDistanceFromWall(RPoint point) {
RBattlefield bf = RampantRobot.getGlobalBattlefield();
if(bf == null)
return -1;
return bf.distanceFromWall(point);
}
/**
* returns the factor index for the statistics array
*/
public static int getFactorIndex(REnemyWave wave, RPoint target, int numBins) {
double offsetAngle = wave.computeOffsetAngle(target);
double factor = Utils.normalRelativeAngle(offsetAngle) / RUtil.computeMaxEscapeAngle(wave.getVelocity()) * wave.getDirection();
return computeBin(factor, numBins);
}
/**
* @param index
* @param arraySize
* @return a guess factor in the range of -1 to 1
*/
public static double getGuessFactorForIndex(int index, int arraySize) {
int offset = index - (arraySize - 1) / 2;
return (double) offset / (double) ((arraySize - 1) / 2);
}
/**
* @param arr
* @return the index of the largest element in the array
*/
public static int indexOfLargest(double[] arr) {
double largest = 0;
int largestIndex = (arr.length - 1) /2;
for(int i = 0; i < arr.length; i++) {
if(arr[i] > largest) {
largest = arr[i];
largestIndex = i;
}
}
return largestIndex;
}
/**
* @param arr
* @return the index of the smallest element in the array
*/
public static int indexOfSmallest(double[] arr) {
double lowest = 50000;
int lowestIndex = -1;
for(int i = 0; i < arr.length; i++) {
if(arr[i] < lowest) {
lowest = arr[i];
lowestIndex = i;
}
}
return lowestIndex;
}
/**
* Determines if the passed value is within the range
* @param min
* @param max
* @param value
* @return
*/
public static boolean inRange(double min, double max, double value) {
return (value >= min && value <= max);
}
/**
* Determines if the enemy is advancing (mainly to counter ram bots)
* @param absoluteBearing
* @param enemyHeading
* @param deltaHeading
* @param velocity
* @return {@code true} if the enemy is advancing
*/
public static boolean isAdvancing(double absoluteBearing, double enemyHeading, double deltaHeading, double velocity) {
if(inRange(-Constants.RAM_TOLERANCE, Constants.RAM_TOLERANCE, Math.abs(enemyHeading - absoluteBearing) - Math.PI)) {
return (Math.abs(deltaHeading) < Constants.DELTA_TOLERANCE && velocity > 1);
}
return false;
}
/**
* Limits the passed value to the range between the passed max
* and min values
* @param min
* @param value
* @param max
* @return the limited value
*/
public static double limit(double min, double value, double max) {
return Math.max(min, Math.min(value, max));
}
/**
* @param values
* @return the lowest value from the list of values
*/
public static double lowest(double[] values) {
double lowest = values[0];
for(int i = 1; i < values.length; i++)
if(lowest > values[i])
lowest = values[i];
return lowest;
}
public static int sign(double d) {
return (d < 0 ? -1 : d > 0 ? 1 : 0);
}
/**
* Returns 1 or -1 given the passed value
* @param d
* @return
*/
public static int nonZeroSign(double d) {
if (d < 0) { return -1; }
return 1;
}
/**
* Projects a point location given distance and angle
*/
public static RPoint project(RPoint source, double angle, double length) {
double x = source.x + Math.sin(angle) * length;
double y = source.y + Math.cos(angle) * length;
return new RPoint(x, y);
}
/**
* Computes the rolling average
* @param value
* @param newEntry
* @param n
* @param weighting
* @return
*/
public static double rollingAvg(double value, double newEntry, double n, double weighting ) {
return (value * n + newEntry * weighting)/(n + weighting);
}
/**
* Rounds the specified value to the passed float precision
* @param value
* @param precision
* @return the rounded value
*/
public static double roundToPrecision(double value, int precision) {
int temp = (int) Math.round((value * Math.max(1, Math.pow(10, precision))));
return ((double) temp) / Math.pow(10, precision);
}
/**
* Scales a given value to fall within the given min and max values
* @param min
* @param max
* @param minExpected
* @param maxExpected
* @param value
* @return
*/
public static double scaleToRange(double min, double max, double minExpected, double maxExpected, double value) {
return scaleToRange(min, max, minExpected, maxExpected, value, 0);
}
/**
* Scales a given value to fall within the given min and max values
* @param min
* @param max
* @param minExpected
* @param maxExpected
* @param value
* @param offset
* @return
*/
public static double scaleToRange(double min, double max, double minExpected, double maxExpected, double value, double offset) {
double scaleRange = max - min;
double valueRange = maxExpected - minExpected;
double percentDiff = scaleRange / valueRange;
return RUtil.limit(min, (value + offset) * percentDiff, max);
}
/**
* Credit: Voidious
* @param robot
* @param goAngle
* @param dist
*/
public static void setBackAsFront(AdvancedRobot robot, double goAngle, double dist) {
double angle = Utils.normalRelativeAngle(goAngle - robot.getHeadingRadians());
if (Math.abs(angle) > (Math.PI/2)) {
if (angle < 0) {
robot.setTurnRightRadians(Math.PI + angle);
} else {
robot.setTurnLeftRadians(Math.PI - angle);
}
robot.setBack(dist);
} else {
if (angle < 0) {
robot.setTurnLeftRadians(-1*angle);
} else {
robot.setTurnRightRadians(angle);
}
robot.setAhead(dist);
}
}
/**
* @param value
* @return the square of the tpassed value
*/
public static double square(double value) {
return Math.pow(value, 2);
}
/**
* Computes the sum of two vectors
* @param arr1
* @param arr2
* @return the sum of the two vectors
*/
public static double[] sum(double[] arr1, double[] arr2) {
double[] sum = new double[arr1.length];
if(arr1.length != arr2.length) {
System.err.print("Sum error: arrays not the same length!");
return null;
}
for(int i = 0; i < arr1.length; i++)
sum[i] = arr1[i] + arr2[1];
return sum;
}
public static double[] average(double[] arr1, double[] arr2) {
double[] sum = sum(arr1, arr2);
if(sum == null)
return null;
for(int i = 0; i < sum.length; i++)
sum[i] /= 2;
return sum;
}
/**
* Handles wall smoothing in one pass, no iterations
* @param location
* @param goAngle
* @param direction
* @param distanceToCenterOfOrbit
* @return the wall smoothed angle
*/
public static double wallSmoothing(RPoint location, double goAngle, int direction, double distanceToCenterOfOrbit) {
RBattlefield bf = RampantRobot.getGlobalBattlefield();
double wallStick = Math.min(distanceToCenterOfOrbit, WALL_STICK);
RPoint projectedLocation = project(location, goAngle, wallStick);
if(bf.contains(projectedLocation))
return goAngle; // no change needed
double topDist = bf.innerDistanceFromTop(location);
double rightDist = bf.innerDistanceFromRight(location);
double leftDist = bf.innerDistanceFromLeft(location);
double botDist = bf.innerDistanceFromBot(location);
boolean top = topDist <= wallStick;
boolean bot = botDist <= wallStick;
boolean right = rightDist <= wallStick;
boolean left = leftDist <= wallStick;
boolean clockwise = direction > 0;
boolean smoothTop = top && (!(right || left) || (right && !clockwise) || (left && clockwise));
boolean smoothBot = bot && (!(right || left) || (left && !clockwise) || (right && clockwise));
boolean smoothRight = right && (!(top || bot) || (bot && !clockwise) || (top && clockwise));
boolean smoothLeft = left && (!(top || bot) || (top && !clockwise) || (bot && clockwise));
double newAngle;
double tolerance = 2;
if(smoothTop) {
newAngle = (topDist < tolerance ? Math.PI / 2 : Math.acos(topDist / wallStick)) * direction;
} else if(smoothBot) {
newAngle = (botDist < tolerance ? Math.PI / 2 : Math.acos(botDist / wallStick)) * direction + Math.PI;
} else if(smoothRight) {
newAngle = (rightDist < tolerance ? Math.PI / 2 : Math.acos(rightDist / wallStick)) * direction + Math.PI / 2;
} else if(smoothLeft) {
newAngle = (leftDist < tolerance ? Math.PI / 2 : Math.acos(leftDist / wallStick)) * direction + 3 * Math.PI / 2;
} else {
System.err.println("Smoothing Error!");
return 0;
}
return newAngle;
}
/**
* @param deltaH
* @param deltaH2
* @return
*/
public static double percentDifference(double v1, double v2) {
double sum = (v1 + v2) / 2;
if(sum == 0)
return 1;
return Math.abs((v1 - v2) / sum);
}
public static boolean pointOnRobotPoint(RPoint point, RPoint robotLocation) {
int radius = REnemyRobot.BOT_RADIUS;
return (point.x >= robotLocation.x - radius && point.x <= robotLocation.x + radius) && (point.y >= robotLocation.y - radius && point.y <= robotLocation.y + radius);
}
public static boolean pointOnRobot(RPoint point, REnemyRobot enemy) {
RPoint enemyLocation = enemy.getCurrentState().location;
int radius = REnemyRobot.BOT_RADIUS;
return (point.x >= enemyLocation.x - radius && point.x <= enemyLocation.x + radius) && (point.y >= enemyLocation.y - radius && point.y <= enemyLocation.y + radius);
}
}