With a few additions to the bot we built in "Tracking your opponents' movement", we can add factored wall avoidance to an existing or troublesome movement algorithm. Factored wall avoidance attempts to find the best possible heading by factoring together the desired heading with a safe heading based on how close our bot is to a wall.
Adding helper methods for common mathematical calculations
The first things we will add to our robot are a few helper methods for frequently used mathematical algorithms.
The calculateBearingToXYRadians() method uses the java.lang.Math method atan2() to calculate the absolute bearing from sourceX,sourceY to targetX,targetY, then converts this value to a bearing relative to sourceHeading.
We will also require a normalizeAbsoluteAngleRadians() method and a normalizeRelativeAngleRadians() method.
Listing 1. Math helper methods
private static final double DOUBLE_PI = (Math.PI * 2);
private static final double HALF_PI = (Math.PI / 2);
public double calculateBearingToXYRadians(double sourceX, double sourceY,
double sourceHeading, double targetX, double targetY) {
return normalizeRelativeAngleRadians(
Math.atan2((targetX - sourceX), (targetY - sourceY)) -
sourceHeading);
}
public double normalizeAbsoluteAngleRadians(double angle) {
if (angle < 0) {
return (DOUBLE_PI + (angle % DOUBLE_PI));
} else {
return (angle % DOUBLE_PI);
}
}
public static double normalizeRelativeAngleRadians(double angle) {
double trimmedAngle = (angle % DOUBLE_PI);
if (trimmedAngle > Math.PI) {
return -(Math.PI - (trimmedAngle % Math.PI));
} else if (trimmedAngle < -Math.PI) {
return (Math.PI + (trimmedAngle % Math.PI));
} else {
return trimmedAngle;
}
}
|
Extending AdvancedRobot with back-as-front functionality
Next, we need to extend the AdvancedRobot class functionality with some helper methods to allow back-as-front operations for navigating in reverse:
- The
getRelativeHeading()method handles the overhead of calculating the correct heading relative to the bot's current direction.
- The
reverseDirection()method is fairly straightforward. It toggles thedirectioninstance variable and reverses the bot's direction. Note that because it takes time to decelerate, the bot may continue to move in the same direction for up to four frames before reversing, depending on its velocity.
- The
setAhead()andsetBack()methods override the methods of the same name in theAdvancedRobotclass. They set the speed of the bot relative to the current direction and adjust thedirectioninstance variable as necessary. We do this to ensure that relative operations are always in relation to the direction the bot is currently moving.
- The
setTurnLeftRadiansOptimal()andsetTurnRightRadiansOptimal()methods reverse the direction of the bot for turns greater than(Math.PI / 2). You will want to use these methods when using theadjustHeadingForWallsmethod, which we will discuss later.
Note: I access the direction instance variable directly instead of using getter and setter methods. This is not generally a good programming practice, but I often do it in my bot code to speed up data access.
Listing 2. Robot helper methods
public double getRelativeHeadingRadians() {
double relativeHeading = getHeadingRadians();
if (direction < 1) {
relativeHeading =
normalizeAbsoluteAngleRadians(relativeHeading + Math.PI);
}
return relativeHeading;
}
public void reverseDirection() {
double distance = (getDistanceRemaining() * direction);
direction *= -1;
setAhead(distance);
}
public void setAhead(double distance) {
double relativeDistance = (distance * direction);
super.setAhead(relativeDistance);
if (distance < 0) {
direction *= -1;
}
}
public void setBack(double distance) {
double relativeDistance = (distance * direction);
super.setBack(relativeDistance);
if (distance > 0) {
direction *= -1;
}
}
public void setTurnLeftRadiansOptimal(double angle) {
double turn = normalizeRelativeAngleRadians(angle);
if (Math.abs(turn) > HALF_PI) {
reverseDirection();
if (turn < 0) {
turn = (HALF_PI + (turn % HALF_PI));
} else if (turn > 0) {
turn = -(HALF_PI - (turn % HALF_PI));
}
}
setTurnLeftRadians(turn);
}
public void setTurnRightRadiansOptimal(double angle) {
double turn = normalizeRelativeAngleRadians(angle);
if (Math.abs(turn) > HALF_PI) {
reverseDirection();
if (turn < 0) {
turn = (HALF_PI + (turn % HALF_PI));
} else if (turn > 0) {
turn = -(HALF_PI - (turn % HALF_PI));
}
}
setTurnRightRadians(turn);
}
|
Adding factored wall avoidance
The last method we need to add is adjustHeadingForWalls().
The first half of this method chooses a safe x,y position based on the bot's proximity to the walls (this will be the bot's current x or y location or a center point if the bot is near a wall). The second half of the method calculates the bearing to the "safe" point and factors this in with the desired heading relative to how close the bot is to a wall.
The degree to which the bot worries about the walls can be adjusted using the WALL_AVOID_INTERVAL and WALL_AVOID_FACTORS constants.
Listing 3. The wall avoidance method
private static final double WALL_AVOID_INTERVAL = 10;
private static final double WALL_AVOID_FACTORS = 20;
private static final double WALL_AVOID_DISTANCE =
(WALL_AVOID_INTERVAL * WALL_AVOID_FACTORS);
private double adjustHeadingForWalls(double heading) {
double fieldHeight = getBattleFieldHeight();
double fieldWidth = getBattleFieldWidth();
double centerX = (fieldWidth / 2);
double centerY = (fieldHeight / 2);
double currentHeading = getRelativeHeadingRadians();
double x = getX();
double y = getY();
boolean nearWall = false;
double desiredX;
double desiredY;
// If we are too close to a wall, calculate a course toward
// the center of the battlefield.
if ((y < WALL_AVOID_DISTANCE) ||
((fieldHeight - y) < WALL_AVOID_DISTANCE)) {
desiredY = centerY;
nearWall = true;
} else {
desiredY = y;
}
if ((x < WALL_AVOID_DISTANCE) ||
((fieldWidth - x) < WALL_AVOID_DISTANCE)) {
desiredX = centerX;
nearWall = true;
} else {
desiredX = x;
}
// Determine the safe heading and factor it in with the desired
// heading if the bot is near a wall
if (nearWall) {
double desiredBearing =
calculateBearingToXYRadians(x,
y,
currentHeading,
desiredX,
desiredY);
double distanceToWall = Math.min(
Math.min(x, (fieldWidth - x)),
Math.min(y, (fieldHeight - y)));
int wallFactor =
(int)Math.min((distanceToWall / WALL_AVOID_INTERVAL),
WALL_AVOID_FACTORS);
return ((((WALL_AVOID_FACTORS - wallFactor) * desiredBearing) +
(wallFactor * heading)) / WALL_AVOID_FACTORS);
} else {
return heading;
}
}
|
The rest is easy. We can use our current navigation algorithm and feed the result through the adjustHeadingForWalls() method to avoid the walls.
To keep things simple, the example bot (see Download to download the source code needed to add this technique) will always attempt to move in a straight line by requesting a heading change of 0.
Listing 4. Wall avoidance method
public void run() {
while(true) {
setTurnRightRadiansOptimal(adjustHeadingForWalls(0));
setAhead(100);
execute();
}
}
|
That's all there is to it. Simple, but effective.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code | j-fwa.zip | 4KB | HTTP |
Information about download methods
- Read all of the Secrets from the Robocode masters. This page will be updated as new tips become available.
- Visit the official Robocode site for more information.
- RoboLeague by Christian Schnell is a league and season manager for Robocode. It ensures that all possible groupings indeed play their matches, manages the results, and produces HTML status reports.
- "Rock 'em, sock 'em Robocode" (developerWorks, January 2002) disarms Robocode and starts you on your way to building your own customized lean, mean, fighting machine.
- In "Rock 'em, sock 'em Robocode: Round 2" (developerWorks, May 2002), Sing Li looks at advanced robot construction and team play.
- New to Java? Check out "Introduction to Java programming" (developerWorks, November 2004), a tutorial that steps you through the fundamentals of Java language programming.
- developerWorks: Hundreds of articles about every aspect of Java programming.




