CassiusClay/Butterfly
Butterfly by PEZ - The CassiusClay (pluggable) movement
Butterfly is all about WaveSurfing. It's the result of letting the Pugilist movement grow out of its MiniBot constraints. The design is pretty simple:
- Every scan create an EnemyWave
- If the enemy fired the tick before then mark the wave as "surfable"
- If a bullet hits my bot then
- Find the wave that is in the passing
- Find out what GuessFactor we were hit on
- Update the stat buffers with this knowledge
- Every scan:
- check where (what guess factor) we would impact with the closest surfable wave were we to:
- move fully forward
- move fully reverse
- hit the breaks
- For each wave in the air
- Examine how often we've been hit in each of these destination options compared to all other guess factors
- Weigh together the results of all surfable waves with the closest one being the most important
- Go for the option where we've been hit the least (or where it seems the least dangerous to move)
- check where (what guess factor) we would impact with the closest surfable wave were we to:
Simple enough, huh? One of those steps is a bit complicated though - step 2.1. It involves predicting our own movement some ticks in the future. This can be done in many ways. Pugilist does it in a pretty sloppy manner. CassiusClay is a bit more precise. In fact as of version 1.9.9.00g (g for gamma, my first test version) not only considers how fast CC can accelerate/deccelerate. It also tries to take into account the maximum turn angle it can do at a given velocity. Here's the scheme for the predictor:
Inputs
- The closest "surfable" EnemyWave
- The desired orbit direction
- The desired target velocity (between 0 and 8 inclusive)
Action
- Grab the current heading
- Grab the current velocity (relative to the desired orbit direction)
- Do
- Calculate a WallSmoothed position in the desired orbit direction
- Calculate absolute bearing to this position
- Adjust the velocity according to Robocode physics (aiming for the target velocity)
- Calculate how much we would need to turn the bot to get the right heading
- Adjust our virtual heading while considering BackAsFront and the maximum turn rate at the current virtual velocity
- Calculate the position of the bot 1 tick into the future using the new virtual heading and velocity
- Advance the wave 1 tick into the future
- Until the wave impacts with the predicted bot position
Here's the implementing code (RWPCL):
Move waveImpactLocation(MovementWave closest, double direction, double maxVelocity) {
double currentDirection = robotOrbitDirection(closest.gunBearing(robotLocation));
double v = Math.abs(robot.getVelocity()) * PUtils.sign(direction);
double h = robot.getHeadingRadians();
Point2D orbitCenter = orbitCenter(closest);
Point2D impactLocation = new Point2D.Double(robot.getX(), robot.getY());
Move smoothed;
int time = 0;
do {
smoothed = wallSmoothedDestination(impactLocation, orbitCenter, currentDirection * direction);
double wantedHeading = PUtils.absoluteBearing(impactLocation, smoothed.location);
h += PUtils.backAsFrontDirection(wantedHeading, h) < 0 ? Math.PI : 0.0;
if (v < maxVelocity) {
v = Math.min(maxVelocity, v + (v < 0 ? 2 : 1));
}
else {
v = Math.max(maxVelocity, v - 2);
}
double maxTurn = Math.toRadians(MAX_TURN_RATE - 0.75 * Math.abs(v));
h += PUtils.minMax(PUtils.backAsFrontTurn(wantedHeading, h), -maxTurn, maxTurn);
impactLocation = PUtils.project(impactLocation, h, v);
} while (closest.distanceFromTarget(impactLocation, time++) > 18);
return new Move(impactLocation, smoothed.smoothing, smoothed.wantedEvasion, smoothed.oldDistance, impactLocation.distance(enemyLocation));
}
The BackAsFront functions there looks like so:
public static double backAsFrontTurn(double newHeading, double oldHeading) {
return Math.tan(newHeading - oldHeading);
}
public static double backAsFrontDirection(double newHeading, double oldHeading) {
return sign(Math.cos(newHeading - oldHeading));
}
It's the same functions I use to move my bot around Jamougha style:
double newHeading = PUtils.absoluteBearing(robotLocation, destination);
double oldHeading = robot.getHeadingRadians();
robot.setAhead(PUtils.backAsFrontDirection(newHeading, oldHeading) * 50);
robot.setTurnRightRadians(PUtils.backAsFrontTurn(newHeading, oldHeading));