Robocode Lesson #5: Movement Basics

This is your last lesson, and to round things out, we come back to where we started in Lesson #2, moving your robot. We'll bring it on home this week by showing you how to close in on your enemy, dodge bullets, and avoid walls.

Sideways Thinking

If you've played basketball before, you know that if you want to defend someone holding the ball, you want to maximize your lateral movement by always squaring off against them (facing them). The same is true with your robot as the following picture illustrates:

The yellow robot can easily move side to side to evade the blue robot and dodge the bullets he shoots. The blue robot, by contrast, doesn't have any good place to go: if he moves back, he gets shot at by the yellow bot, if he moves forward to try to ram, the yellow bot can just scoot out of the way.

Squaring Off

To square off against an opponent, use the following code:

setTurnRight(enemy.getBearing() + 90);
which will always place your robot perpendicular (90 degrees) to your enemy.

Forward or Backward?

When you're squared off against an opponent, the ideas of "forward" and "backward" become somewhat obsolete. You're probably thinking more in terms of "strafe left" or "strafe right". To keep track of the movement direction, just declare a variable like we did for oscillating the radar.

class MyRobot extends AdvancedRobot {
	private byte moveDirection = 1;
then, when you want to move your robot, you can just say:
setAhead(100 * moveDirection);
You can switch directions by changing the value of moveDirection from 1 to -1 like so:
moveDirection *= -1;

Switching Directions

The most intuative approach to switching directions is to just flip the move direction any time you hit a wall or hit another robot like so:

public void onHitWall(HitWallEvent e) { moveDirection *= -1; }
public void onHitRobot(HitRobotEvent e) { moveDirection *= -1; }
However, you will find that if you do that, you'll end up doggedly pressing up against a robot that rams you from the side (like a dog in heat). That's because onHitRobot() gets called so many times that the moveDirection keeps flipping and you never move away.

A better approach is to just test to see if your robot has stopped. If it has, it probably means you've hit something and you'll want to switch direction. You can do it with the code:

if (getVelocity() == 0)
	moveDirection *= -1;
Put that into your doMove() method (or wherever else you're handling movement) and you can handle all wall-hit and robot-hit events.

Shall We Dance?

All the sample robots that follow use the above techniques for moving around their enemies, with some minor variations. You can match them up against any of the sample robots.

Circling

Circling your enemy can be done by simply using the above techniques:

public void doMove() {

	// switch directions if we've stopped
	if (getVelocity() == 0)
		moveDirection *= -1;

	// circle our enemy
	setTurnRight(enemy.getBearing() + 90);
	setAhead(1000 * moveDirection);
}

Sample robot: Circler circles his enemy using the above movement code, rather like a shark circling it's prey in the water.

Strafing

One problem you might notice with Circler is that he is easy prey for predictive targeting because his movements are so... predictable. Match Circler up against PredictiveShooter and watch how quick he goes down.

To evade bullets more effectively, you should move side-to-side or "strafe". A good way to do this is to switch direction after a certain number of "ticks", like so:

public void doMove() {

	// always square off against our enemy
	setTurnRight(enemy.getBearing() + 90);

	// strafe by changing direction every 20 ticks
	if (getTime() % 20 == 0) {
		moveDirection *= -1;
		setAhead(150 * moveDirection);
	}
}
Oddly, MyFirstRobot does something along these lines and can be surprisingly hard to hit.

Sample robot: Strafer rocks back and forth useing the above movement code. Notice how nicely he dodges bullets.

Closing In

You'll notice that both Circler and Strafer have another problem: they get stuck in the corners easy and end up just banging into the walls. An additional problem is that if their enemy is distant, they shoot a lot but don't hit a lot.

To make your robot close in on your enemy, just modify the "squaring off" code to make him turn in toward his enemy slightly, like so:

setTurnRight(normalizeBearing(enemy.getBearing() + 90 - (15 * moveDirection)));

Sample robot: Spiraler is a variation on Circler that uses the above code to spiral in toward his enemy.

Sample robot: StrafeCloser is a variant on Strafer that uses the above code to strafe ever closer. He's a pretty good bullet-dodger, too.

Note that neither of the above robots gets caught in a corner for very long.

Avoiding Walls

A problem with all of the above robots is that they hit the walls a lot, and hitting the walls drains your energy. A better strategy would be to stop before you hit the walls. But how?

Adding a Custom Event

The first thing you need to do is decide how close we will allow our robot to get to the walls:

public class WallAvoider extends AdvancedRobot {
	...
	private int wallMargin = 60; 
Next, we add a custom event that will be fired when a certain condition is true:
// Don't get too close to the walls
addCustomEvent(new Condition("too_close_to_walls") {
		public boolean test() {
			return (
				// we're too close to the left wall
				(getX() <= wallMargin ||
				 // or we're too close to the right wall
				 getX() >= getBattleFieldWidth() - wallMargin ||
				 // or we're too close to the bottom wall
				 getY() <= wallMargin ||
				 // or we're too close to the top wall
				 getY() >= getBattleFieldHeight() - wallMargin)
				);
			}
		});
Note that we are creating an anonymous inner class with this call. (You guys will do a lot of this sort of thing when we do GUI stuff.) We need to override the test() method to return a boolean when our custom event occurs.

Handling the Custom Event

The next thing we need to do is handle the event, which can be done like so:

public void onCustomEvent(CustomEvent e) {
	if (e.getCondition().getName().equals("too_close_to_walls"))
	{
		// switch directions and move away
		moveDirection *= -1;
		setAhead(10000 * moveDirection);
	}
}
The problem with that approach, though is that this event could get fired over and over, causing us to rappidly switch back and forth, never actually moving away.

Sample robot: JiggleOfDeath demonstrates the flaw in the above approach. Match him up against Walls and watch him go down.

To avoid this "jiggle of death" we should have a variable that indicates that we're handling the event. We can declare another like so:

public class WallAvoider extends AdvancedRobot {
	...
	private int tooCloseToWall = 0;
Then handle the event a little smarter:
public void onCustomEvent(CustomEvent e) {
	if (e.getCondition().getName().equals("too_close_to_walls"))
	{
		if (tooCloseToWall <= 0) {
			// if we weren't already dealing with the walls, we are now
			tooCloseToWall += wallMargin;
			setMaxVelocity(0); // stop!!!
		}
	}
}

Handling the Two Modes

There are two last problems we need to solve. Firstly, we have a doMove() method where we put all our normal movement code. If we're trying to get away from a wall, we don't want our normal movement code to get called, creating (once again) the "jiggle of death". Secondly, we want to eventually return to "normal" movement, so we should have the tooCloseToWall variable "time out" eventually.

We can solve both these problems with the following doMove() implementation:

public void doMove() {
	// always square off against our enemy, turning slightly toward him
	setTurnRight(enemy.getBearing() + 90 - (10 * moveDirection));

	// if we're close to the wall, eventually, we'll move away
	if (tooCloseToWall > 0) tooCloseToWall--;

	// normal movement: switch directions if we've stopped
	if (getVelocity() == 0) {
		moveDirection *= -1;
		setAhead(10000 * moveDirection);
	}
}

Sample robot: WallAvoider uses all the above code to avoid running into the walls. Match him up against Walls and note how he gently glides toward the sides but never (well, rarely) hits them.

Multi-Mode Bot

Besides the colors you chose, the biggest part of your robot's personality is in his movement code. On the other hand, different situations call for different tactics. Using the wall-avoiding as an example, you may want to code your bot to change "modes" based on certain criteria. Using your Lab 5 exercise as a starting point, I can picture a robot with a method like this in it:

public void onRobotDeath(RobotDeathEvent e) {
	...
	if (getOthers() > 10) {
		// a large group calls for fluid movement
		tank = new CirclingTank();
	} else if (getOthers() > 1) {
		// dodging is the best small-group tactic
		tank = new DodgingTank();
	} else if (getOthers() == 1) {
		// if there's only one bot left, hunt him down
		tank = new SeekAndDestroy();
	}
	...
}   
The details are left (as always) as an exercise for the student.