Darkcanuck/VelocityTest
Jump to navigation
Jump to search
Some code to test 1.7.1.3's updated getNewVelocity() method vs a few wiki-contributed versions.
The main class:
import robocode.Rules;
public class VelocityTest {
private static Commands currentCommands = new Commands();
private static boolean doPrint = false;
public static void main(String[] args) {
// init
System.out.println("\nDarkcanuck's VelocityTest\n");
if (!args[0].equals("all") && !args[0].equals("print")) {
doPrint = true;
double velocity = (args.length>0) ? Double.valueOf(args[0]) : 0.0;
double distance = (args.length>1) ? Double.valueOf(args[1]) : 0.0;
double maxvel = (args.length>2) ? Double.valueOf(args[2]) : -1.0;
int exptime = (args.length>3) ? Integer.valueOf(args[3]) : 0;
testCase(velocity, distance, maxvel, exptime);
} else {
if (args[0].equals("print"))
doPrint = true;
testCase( 8.0, 22.8, -1, 5); // Simonton's caveat #1
testCase( 8.0, 22.5, -1, 5); // Simonton's caveat #2
testCase( 8.0, -200.0, 7.0, 39); // Simonton's caveat #3
testCase( 0.0, 6.0, -1, 4); // overshoot
testCase( 0.0, 10.0, -1, 6); // overshoots twice
testCase( 4.0, 0.0, -1, 4); // second, unnecessary overshoot
testCase(-1.9, 10.0, -1, 6); // impossible decel/accel!
}
}
public static void testCase(double velocity, double distance, double maxvel, int exptick) {
StringBuffer b = new StringBuffer();
boolean passed = true;
double newvel = velocity;
double newdist = distance;
double maximum = (maxvel>=0) ? maxvel : Rules.MAX_VELOCITY;
int tick = 0;
int maxtick = (exptick>0) ? exptick : (int) Math.max(Math.abs(distance), 100);
b.append("Starting velocity=" + velocity + " distance=" + distance +
((maxvel>=0)? " max=" + maxvel : "") + "\n");
currentCommands.setMaxVelocity(maximum);
while ((Math.abs(newvel)>0.00001) || (Math.abs(newdist)>0.00001)) {
// test next velocity change
//double nextvel = orig_getNewVelocity(newvel, newdist);
//double nextvel = void_getNewVelocity(newvel, newdist);
//double nextvel = skil_getNewVelocity(newvel, newdist);
//double nextvel = posi_getNewVelocity(newvel, newdist);
double nextvel = vopo_getNewVelocity(newvel, newdist);
passed &= checkAccel(nextvel, newvel) && checkMax(nextvel, maximum);
// apply changes
newvel = nextvel;
newdist -= nextvel;
tick++;
b.append(" " + tick + " velocity=" + newvel + " remain=" + newdist + "\n");
// check for infinite loops
if (tick > 2*maxtick)
break;
}
// check time taken
passed &= (tick-1 <= maxtick);
if (!passed)
b.append(" FAIL\n");
if (!passed || doPrint)
System.out.println(b.toString() + "\n");
}
private static boolean checkAccel(double newvel, double oldvel) {
double reverse = (oldvel<0) ? -1.0 : 1.0;
double min = oldvel - reverse*Rules.DECELERATION;
double max = oldvel + reverse*Rules.ACCELERATION;
if (oldvel==0)
min = oldvel - reverse*Rules.ACCELERATION;
if (oldvel<0)
return (newvel>=max) && (newvel<=min);
else
return (newvel<=max) && (newvel>=min);
}
private static boolean checkMax(double newvel, double maxvel) {
return (Math.abs(newvel) <= Math.abs(maxvel));
}
/****************** ORIGINAL FROM ROBOCODE 1.7.1.3 *********************/
private static double orig_getNewVelocity(double velocity, double distance) {
// If the distance is negative, then change it to be positive and change the sign of the input velocity and the result
if (distance < 0) {
return -orig_getNewVelocity(-velocity, -distance);
}
double newVelocity;
// Get the speed, which is always positive (because it is a scalar)
final double speed = Math.abs(velocity);
// Check if we are decelerating, i.e. if the velocity is negative.
// Note that if the speed is too high due to a new max. velocity, we must also decelerate.
if (velocity < 0 || speed > currentCommands.getMaxVelocity()) {
// If the velocity is negative, we are decelerating
newVelocity = speed - Rules.DECELERATION;
// Check if we are going from deceleration into acceleration
if (newVelocity < 0) {
// If we have decelerated to velocity = 0, then the remaining time must be used for acceleration
double decelTime = speed / Rules.DECELERATION;
double accelTime = (1 - decelTime);
// New velocity (v) = d / t, where time = 1 (i.e. 1 turn). Hence, v = d / 1 => v = d
// However, the new velocity must be limited by the max. velocity
newVelocity = Math.min(currentCommands.getMaxVelocity(),
Math.min(Rules.DECELERATION * decelTime * decelTime + Rules.ACCELERATION * accelTime * accelTime,
distance));
// Note: We change the sign here due to the sign check later when returning the result
velocity *= -1;
}
} else {
// Else, we are not decelerating, but might need to start doing so due to the remaining distance
// Deceleration time (t) is calculated by: v = a * t => t = v / a
final double decelTime = speed / Rules.DECELERATION;
// Deceleration time (d) is calculated by: d = 1/2 a * t^2 + v0 * t + t
// Adding the extra 't' (in the end) is special for Robocode, and v0 is the starting velocity = 0
final double decelDist = 0.5 * Rules.DECELERATION * decelTime * decelTime + decelTime;
// Check if we should start decelerating
if (distance <= decelDist) {
// If the distance < max. deceleration distance, we must decelerate so we hit a distance = 0
// Calculate time left for deceleration to distance = 0
double time = distance / (decelTime + 1); // 1 is added here due to the extra 't' for Robocode
// New velocity (v) = a * t, i.e. deceleration * time, but not greater than the current speed
if (time <= 1) {
// When there is only one turn left (t <= 1), we set the speed to match the remaining distance
newVelocity = Math.max(speed - Rules.DECELERATION, distance);
} else {
// New velocity (v) = a * t, i.e. deceleration * time
newVelocity = time * Rules.DECELERATION;
if (speed < newVelocity) {
// If the speed is less that the new velocity we just calculated, then use the old speed instead
newVelocity = speed;
} else if (speed - newVelocity > Rules.DECELERATION) {
// The deceleration must not exceed the max. deceleration.
// Hence, we limit the velocity to the speed minus the max. deceleration.
newVelocity = speed - Rules.DECELERATION;
}
}
} else {
// Else, we need to accelerate, but only to max. velocity
newVelocity = Math.min(speed + Rules.ACCELERATION, currentCommands.getMaxVelocity());
}
}
// Return the new velocity with the correct sign. We have been working with the speed, which is always positive
return (velocity < 0) ? -newVelocity : newVelocity;
}
/************************* FROM Voidious *******************************/
private static double void_getNewVelocity(double velocity, double distance) {
// If the distance is negative, then change it to be positive and change the sign of the input velocity and the result
if (distance < 0) {
return -void_getNewVelocity(-velocity, -distance);
}
double newVelocity;
// Get the speed, which is always positive (because it is a scalar)
final double speed = Math.abs(velocity);
if (velocity < 0) {
// Check if we are decelerating, i.e. if the velocity is negative.
newVelocity = speed - Rules.DECELERATION;
// Check if we are going from deceleration into acceleration
if (newVelocity < 0) {
// If we have decelerated to velocity = 0, then the remaining time must be used for acceleration
double decelTime = speed / Rules.DECELERATION;
double accelTime = (1 - decelTime);
// New velocity (v) = d / t, where time = 1 (i.e. 1 turn). Hence, v = d / 1 => v = d
// However, the new velocity must be limited by the max. velocity
newVelocity = Math.min(Rules.ACCELERATION * accelTime, distance);
// Note: We change the sign here due to the sign check later when returning the result
velocity *= -1;
}
} else {
// Deceleration distance (d) is calculated iteratively due to Robocode's
// discrete time system.
final double decelDist = void_decelDistance(speed);
// Deceleration ticks is the number of ticks it will take to get to
// zero velocity.
final long decelTime = Math.round( // VOIDIOUS: for rounding errors? maybe unnecessary
Math.ceil((speed - Rules.DECELERATION) / Rules.DECELERATION));
// The maximum distance coverable with an equivalent decelTime
final double decelTimeMaxDist = ((decelTime + 1) / 2.0) * decelTime // sum of 1..decelTime
* Rules.DECELERATION;
if (distance <= Rules.DECELERATION) {
// If we can cover remaining distance and then completely stop,
// set speed = distance
newVelocity = Math.max(speed - Rules.DECELERATION, distance);
} else if (distance <= decelTimeMaxDist) {
// If we can cover distance in decelTime, split any extra
// distance (between decelDist and distance) over decelTime
// ticks
newVelocity = speed - Rules.DECELERATION +
((distance - decelDist) / decelTime);
} else {
// If we need more than decelTime ticks, try to spread the
// extra distance over (decelTime + 1) ticks. This will just max
// the acceleration if it needs to (ie, if we need more ticks).
// VOIDIOUS: I think this part would break if Rules.ACCELERATION
// were set above Rules.DECELERATION; we might need an
// extra case or something. Doh. =(
newVelocity = Math.min(speed + Rules.ACCELERATION,
(decelTime * Rules.DECELERATION) +
((distance - decelTimeMaxDist) / Math.max(decelTime + 1, 2)));
}
}
// VOIDIOUS: I think it makes more sense to do this here; no need to decelerate maximally
// if you don't need to to accomodate a new setMaxVelocity.
newVelocity = Math.max(speed - Rules.DECELERATION,
Math.min(speed + Rules.ACCELERATION,
Math.min(newVelocity, currentCommands.getMaxVelocity())));
// Return the new velocity with the correct sign. We have been working with the speed, which is always positive
return (velocity < 0) ? -newVelocity : newVelocity;
}
private static final double void_decelDistance(double speed){
double distance = 0;
while(speed > 0){
speed = Math.max(0,speed - Rules.DECELERATION);
distance += speed;
}
return distance;
}
/**************************** FROM Skilgannon *********************************/
private static double skil_getNewVelocity(double velocity, double distance) {
if (distance < 0)
return -skil_getNewVelocity(-velocity, -distance);
// If the distance is negative, then change it to be positive
// and change the sign of the input velocity and the result
double maxVel = currentCommands.getMaxVelocity();
if (velocity < 0)
return Math.min(velocity + Rules.DECELERATION, maxVel);
//we want to go in the opposite direction, so decelerate
else{
//we are going in the direction we want, test what happens if we accelerate
double accel = Math.min(Rules.ACCELERATION, maxVel - velocity);
//speed up, but not more than max velocity. decel if maxVel is less than velocity.
accel = Math.max(accel, -Rules.DECELERATION);
//prevent excess deceleration due to bot lowering the maxVel while velocity is high
if (distance > skil_decelDistance(velocity + accel))
return velocity + accel;
else if(accel != 0 && distance > skil_decelDistance(velocity))
return velocity;
else{
if(distance < Rules.DECELERATION
//we'll be able to cover remaining distance in 1 tick and then decel to stop
&& velocity - distance <= Rules.DECELERATION
//and our velocity is low enough for us to get to that required velocity
)
return Math.min(distance, maxVel);
//choose the velocity to cover all remaining distance
return Math.max(-maxVel, velocity - Rules.DECELERATION);
//velocity > 0 and we are close enough, so decelerate.
}
}
}
/**
* Returns the linear distance it would take to decelerate from a given positive velocity
*
* @param velocity the positive velocity from which to test
* @return the linear distance required to decelerate to a standstill
*/
private static final double skil_decelDistance(double velocity){
double distance = 0;
while(velocity > 0){
distance += velocity;
velocity = Math.max(0,velocity - Rules.DECELERATION);
}
return distance;
}
/******************** FROM Positive ********************/
static double posi_getNewVelocity(double velocityArg, double distanceArg)
{
double velocity;
double distance;
// Make sure the remaining distance is always positive or zero
// (we switch this back later)
if(distanceArg<0)
{
velocity=-velocityArg;
distance=-distanceArg;
}
else
{
velocity=velocityArg;
distance=distanceArg;
}
// Get the next most positive velocity the robot can reach for next turn
double mostPositiveReachableVelocity = posi_VelocityToMostPositiveVelocity(velocity);
// Get the next most negative velocity the robot can reach for next turn
double mostNegativeReachableVelocity = posi_VelocityToMostNegativeVelocity(velocity);
double highestWantedVelocity = Math.min(currentCommands.getMaxVelocity(), posi_maxSpeedToStopInDisp(distance));
// The real next velocity is limited by what is actually reachable
double nextVelocity = Math.min(mostPositiveReachableVelocity,Math.max(mostNegativeReachableVelocity,highestWantedVelocity ));
// Switch return value back if needed
if(distanceArg<0)
nextVelocity = -nextVelocity;
return nextVelocity;
}
static public double posi_VelocityToMostPositiveVelocity(double velocity)
{
// Returns the most positive reachable velocity from the
// specified velocity in one turn
if(velocity>0)
{
double returnVelocity = velocity+Rules.ACCELERATION;
if(returnVelocity>Rules.MAX_VELOCITY)
return Rules.MAX_VELOCITY;
else
return returnVelocity;
}
else
{
double returnVelocity = velocity+Rules.DECELERATION;
if(returnVelocity>Rules.ACCELERATION)
return Rules.ACCELERATION;
else
return returnVelocity;
}
}
static public double posi_VelocityToMostNegativeVelocity(double velocity)
{
// Returns the most negative reachable velocity from the
// specified velocity in one turn
if(velocity<0)
{
double returnVelocity = velocity-Rules.ACCELERATION;
if(returnVelocity<-Rules.MAX_VELOCITY)
return -Rules.MAX_VELOCITY;
else
return returnVelocity;
}
else
{
double returnVelocity = velocity-Rules.DECELERATION;
if(returnVelocity<-Rules.ACCELERATION)
return -Rules.ACCELERATION;
else
return returnVelocity;
}
}
static private final double[] dispStopArray =
{0.0000,1.0000,2.0000,2.5000,3.0000,
3.5000,4.0000,4.3333,4.6666,5.0000,
5.3333,5.6666,6.0000,6.2500,6.5000,
6.7500,7.0000,7.2500,7.5000,7.7500};
static public double posi_maxSpeedToStopInDisp(double displacement)
{
// Returns the biggest velocity the robot could go to this turn,
// still being able to stop without overshooting. (Or if
// remaining displacement is less than 2, returns that)
// This routine could be improved to match up with robocode's
// older velocity selecting rules.
if(displacement>=0)
{
if(displacement>=20)
return 8.0;
else if(displacement<=2)
return displacement;
else
return dispStopArray[(int)Math.floor(displacement)];
}
else
{
return 0;
}
}
/****************** FROM Voidious + Positive ***********************/
public static double vopo_getNewVelocity(double velocity, double distance) {
if(distance<0)
return -vopo_getNewVelocity(-velocity,-distance);
double highestVelocity = vopo_getMaxVelocity(distance); // highest velocity without overshooting
double wantedVelocity = Math.min(highestVelocity,8.0);
// the actually wanted velocity by the robot is the highest possible,
// limited by what the robot set by the setMaxVelocity command
return vopo_getClosestReachableVelocityToVelocity(velocity, wantedVelocity);
// return whatever is closest to that velocity
}
public static double vopo_getClosestReachableVelocityToVelocity(double currentVelocity,double wantedVelocity)
{
// this function assumes wantedVelocity<=Rules.MAXVELOCITY
// with this function you can basically assume setAhead(Infinity) or setAhead(-Infinity)
// was called, and you determine the next velocity based on the max velocity
// set by the robot. For example, if the current velocity is 0 and the max velocity
// set was 4.0, it would return 1.0. If the current velocity was 8.0, it would return 6.0.
if(wantedVelocity<0)
return -vopo_getClosestReachableVelocityToVelocity(-currentVelocity,-wantedVelocity);
if(currentVelocity<0)
{
double nextVelocity;
// we are travelling the wrong way, decelerate
nextVelocity = currentVelocity + Rules.DECELERATION;
if(nextVelocity>Rules.ACCELERATION)
// make sure we can't jump from -0.1 to 1.9 or something
nextVelocity = Rules.ACCELERATION;
if(nextVelocity>wantedVelocity)
// if the wanted velocity is for example 0.5, limit the velocity to that.
return wantedVelocity;
else
// else return the highest possible
return nextVelocity;
}
else
{
if(currentVelocity>wantedVelocity)
{
// both velocities are positive, but we need to decelerate
double nextVelocity = currentVelocity - Rules.DECELERATION;
if(nextVelocity<wantedVelocity)
// if we can decelerate more than what's wanted, return what's wanted
return wantedVelocity;
else
// else return the closest to it
return nextVelocity;
}
else
{
// the wantedVelocity is higher than current
double nextVelocity = currentVelocity + Rules.ACCELERATION;
if(nextVelocity>wantedVelocity)
// if we can accelerate more than what's wanted, return what's wanted
return wantedVelocity;
else
// else return the closest to it
return nextVelocity;
}
}
}
public static double vopo_getMaxVelocity(double distance)
{
long decelTime = vopo_decelTime(distance);
double decelDist = (decelTime / 2.0) * (decelTime-1) // sum of 0..(decelTime-1)
* Rules.DECELERATION;
return ((decelTime - 1) * Rules.DECELERATION) +
((distance - decelDist) / decelTime);
}
public static long vopo_decelTime(double distance) {
long x = 1;
do {
// (square(x) + x) / 2) = 1, 3, 6, 10, 15...
if (distance <= ((square(x) + x) / 2) * Rules.DECELERATION) {
return x;
}
x++;
} while (true);
}
public static long square(long i) {
return i * i;
}
}
And a support class for setting the max velocity:
import robocode.Rules;
public class Commands {
private static double maxVelocity = Rules.MAX_VELOCITY;
public static double getMaxVelocity() { return maxVelocity; }
public static void setMaxVelocity(double maxVel) { maxVelocity = maxVel; }
}