Robocode Lesson #3: Scanning Basics

In this lesson, we describe the basics of how the scanning works.

Your Lab 3 exercise correpsonds to this robocode lesson.

Robot Senses

We'll begin this lesson by discussing your robot's senses. It has only a few.

Sense of Touch

Your robot knows when it's:

  1. hit a wall (onHitWall),
  2. been hit by a bullet (onHitByBullet),
  3. or hit another robot (onHitRobot).

All of these methods pass you events that give you information about what you touched.

Sense of Sight

Your robot knows when it's seen another robot, but only if it scans it (onScannedRobot).

Scan events are arguably the most important of all events. Scan events give you information about the other robots on the battlefield. (Some robots put 90+% of their code in the onScannedRobot method.) The only way scan events can be generated (practically speaking) is if you move your radar. (If an enemy robot wanders in front of your radar it will generate a scan event, but you should really take a more proactive stance.)

Also, remember per Lesson 2, the scanner is the fastest moving part of your robot, so don't be stingy about moving it.

If you want to, you can make the robots' scan arcs visible by selecting Options Menu -> Preferences -> View Options Tab and click on the "Visible Scan Arcs" checkbox. This is handy when debugging.

Other Senses

Your robot also knows when he's died (onDeath), when another robot has died (onRobotDeath -- we will use this one today), or when he's won the round (onWin -- this is where you write the code for your victory dance).

Your robot also is aware of his bullets and knows when a bullet has hit an opponent (onBulletHit), when a bullet hits a wall (onBulletMissed), when a bullet hits another bullet (onBulletHitBullet).

Building A Better Robot

Here's some basic scanning movement. Note again that we call setAdjustRadarForRobotTurn(true) so as to have independent radar movement.

Serial Movements with The Robot Class

You want to find other robots to kill. To do that, you need to scan for other robots. The simplest approach to scanning is to just turn the radar around and around. Your run method could look something like this:

public void run() {
	setAdjustRadarForRobotTurn(true);
	while (true) {
		turnRadarRight(360);
	}
}   

Indeed, our beloved BearingBot from last week did exactly that: he rotates his radar and moves closer to whoever he scans.

You may have noticed that BearingBot has a large defect that you can see if you match him up against an opponent that moves a lot (like, say, Crazy). He scans an opponent, then moves to where he saw him, and by the time he gets there, the opponent has moved away.

Compound Movements with the AdvancedRobot Class

It would be great if we could do more than one thing at once (scan AND turn AND fire). Thankfully, the powers that be have provided us with a means to accomplish this: The AdvancedRobot base class, which allows us to make non-blocking calls to move the robot and then executes them all as a compound action. Crazy and SpinBot (and oddly enough SittingDuck) are all examples of advanced robots.

To change your robot to an advanced robot, just change your class declaration from:

class MyRobot extends Robot {
	...
to
class MyRobot extends AdvancedRobot {
	...
Now you're inheriting from AdvancedRobot rather than Robot; now you can use the set* methods provided by the AdvancedRobot class. (We will discuss more about inheritance when we cover chapter 5.)

Sample robot: AdvancedBearingBot a great improvement over 'BearingBot', because he can do compound movements.

Sample robot: AdvancedTracker - This is a modification of the 'Tracker' sample robot. Per the source code for Tracker, notice how much he improves when you turn him into an AdvancedRobot.

Important Note: If you make a robot derived from AdvancedRobot, you must call the execute() method to perform all the queued-up actions.

But AdvancedBearingBot has another large defect which you can see if you match him up against more than one opponent: he goes driving all over the battlefield chasing one robot after another and doesn't get a lot accomplished. This is because his radar keeps scanning robots, and he chases every one he scans. In short, he lacks focus.

Locking Onto an Enemy

Narrow Beam

We can easily lock onto our opponent by constantly turning the radar toward him whenever we scan him. Intuatively, you might think of doing something with the scanned robot's bearing like so:

    public void onScannedRobot(ScannedRobotEvent e) {
		// Lock on to our target (I hope...)
		 setTurnRadarRight(e.getBearing());
		 ...

There's a problem with this, though: the ScannedRobotEvent gives us a bearing to the scanned robot but it is relative to our tank's position, not our radar's position. How do we resolve this little quandry?

Easy: we find the difference between our tank heading (getHeading()) and our radar heading (getRadarHeading()) and add the bearing to the scanned robot (e.getBearing()), like so:

    public void onScannedRobot(ScannedRobotEvent e) {
		// Lock on to our target (this time for sure)
		setTurnRadarRight(getHeading() - getRadarHeading() + e.getBearing());
		...

Sample robot: NarrowBeam - Uses the above source to lock onto an opponent and nail him. Match him up against as many opponents as you want.

A recurring theme in the computer industry is that the solution to one problem leads to the creation of another. Fittingly, having solved the problem of how to lock onto a target, we are now faced with another problem, which the following screenshot illustrates:

Note that NarrowBeam has locked on so tightly to Crazy that he is blithely unaware that Tracker is killing him from behind.

Oscillating (or "Wobbling") the Radar

In this technique, every time you see an opponent, you whipsaw the radar back so as to focus on one robot and continuously generate scan events. This is an improvement over the narrow beam because you are more aware of nearby robots.

To make this work, you need a variable that keeps track of which direction to turn the radar, it will only ever have values of 1 and -1, so it can be small. You can declare it in your robot like so:

class MyRobot extends AdvancedRobot {
	private byte scanDirection = 1;
	...
The run method can look just like the one above, but in the onScannedRobot method you do the following:
public void onScannedRobot(ScannedRobotEvent e) {
	...
	scanDirection *= -1; // changes value from 1 to -1
	setTurnRadarRight(360 * scanDirection);
	...
Flipping the value of scanDirection creates the oscillating effect.

Sample robot: Oscillator - wobbles his radar to keep track of hist opponent. Note that while he tends to track an enemy, he'll chase others that are nearby, too.

But we still haven't completely solved the problem with NarrowBeam: other robots can still sneak up behind Oscillator and shoot him. Also, Oscillator tends to get a little unfocused, at times.

Enemy Tracking

Using the EnemyBot Class

A further improvement that could be made would be to single out an individual robot, focus on him, and destroy him completely. The sample robot Tracker does this to a limited extent, but we'll do one better with the EnemyBot class you guys all wrote.

To keep track of the information about an enemy robot, you first need to make a member variable in your robot like so:

class MyRobot extends AdvancedRobot {
	private EnemyBot enemy = new EnemyBot();
	...
You will want to reset (clear) your enemy at the top of your run method like so:
public void run() {
	setAdjustRadarForRobotTurn(true);
	enemy.reset();
	while (true) {
		setTurnRadarRight(360);
		...
		execute();
	}
}   
And you need to update the enemy's information in the onScannedRobot method like so:
public void onScannedRobot(ScannedRobotEvent e) {
	if (enemy.none() || e.getName().equals(enemy.getName())) {
		enemy.update(e);
	}
	...
From this point, you can use all of the information about the enemy in any other method of your class.

There is one last detail, though, if the enemy you're tracking dies, you'll want to reset it so you can track another. To do that, implement an onRobotDeath method like so:

public void onRobotDeath(RobotDeathEvent e) {
	if (e.getName().equals(enemy.getName())) {
		enemy.reset();
	}
}

Sample robot: EnemyTracker - a robot that uses the EnemyBot class. Note that even though he rotates the radar, he just tracks one enemy. This allows him to keep an eye on what's going on in the rest of the battlefield while concentrating on his target.

Slightly Smarter Tracking

Another small optimization could be made here: If a closer robot moves into view, we probably want to start shooting him instead. You can accomplish this by modifying your onScannedRobot method like so:

public void onScannedRobot(ScannedRobotEvent e) {

	if (
		// we have no enemy, or...
		enemy.none() ||
		// the one we just spotted is closer, or...
		e.getDistance() < enemy.getDistance() ||
		// we found the one we've been tracking
		e.getName().equals(enemy.getName())
		) {
		// track him
		enemy.update(e);
	}
	...

Sample robot: EnemyOrCloser uses the above scanning technique to hit the closest enemy. As he rotates his radar, he will begin tracking any enemy that is closer (even if someone sneaks up behind him).