[Home]GFTargetingBot

Robo Home | Changes | Preferences | AllPages

GFTargetingBot, by PEZ

What's special about it?

It's a simple StatisticalTargeting bot using GuessFactors, if not literal so the general idea. My goal is that people who want to start out with StatisticalTargeting can do so with this bot without having to figure it all out first. This bot is quite competetive out-of-the-box, but with just some little work it can be made to kick ass.

It features:

It lacks:

Great, I want to try it. Where can I download it?

https://www.robocoderepository.com/BotDetail.jsp?id=2913

How competitive is it?

Quite so for something this simple

How does it move?

Random, fluid, orbit movement. Almost verbatim Aristocles movement. (Minus MusashiTrick)

How does it fire?

A GuessFactorTargeting gun segmented on distance, velocity, last-scan-velocity.

How does it dodge bullets?

It tries to create a flat MovementProfile.

How does the melee strategy differ from one-on-one strategy?

This bot doesn't know about Melee battles.

What does it save between rounds and matches?

The visit counts are saved between rounds. Nothing is saved between matches.

Where did you get the name?

Duh... It's a GuessFactorTargeting tutorial bot.

Can I use your code?

Sure. That's the whole idea. It's included in the jar. Use it as you see fit. Of course if I do not mind credits. And here's the code for version 1.0 (meaning it might not always be up to date.):
package wiki.tutorial;
import robocode.*;
import robocode.util.Utils;
import java.awt.Color;
import java.awt.geom.*;

// GFTargetingBot, by PEZ. A simple GuessFactorTargeting bot for tutorial purposes.
// Use the code as you see fit. Of course if I do not mind credits.

public class GFTargetingBot extends AdvancedRobot {
	private static final double BULLET_POWER = 1.9;
	
	private static double lateralDirection;
	private static double lastEnemyVelocity;
	private static GFTMovement movement;
	
	public GFTargetingBot() {
		movement = new GFTMovement(this);	
	}
	
	public void run() {
		setColors(Color.BLUE, Color.BLACK, Color.YELLOW);
		lateralDirection = 1;
		lastEnemyVelocity = 0;
		setAdjustRadarForGunTurn(true);
		setAdjustGunForRobotTurn(true);
		do {
			turnRadarRightRadians(Double.POSITIVE_INFINITY); 
		} while (true);
	}
	
	public void onScannedRobot(ScannedRobotEvent e) {
		double enemyAbsoluteBearing = getHeadingRadians() + e.getBearingRadians();
		double enemyDistance = e.getDistance();
		double enemyVelocity = e.getVelocity();
		if (enemyVelocity != 0) {
			lateralDirection = GFTUtils.sign(enemyVelocity * Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing));
		}
		GFTWave wave = new GFTWave(this);
		wave.gunLocation = new Point2D.Double(getX(), getY());
		GFTWave.targetLocation = GFTUtils.project(wave.gunLocation, enemyAbsoluteBearing, enemyDistance);
		wave.lateralDirection = lateralDirection;
		wave.bulletPower = BULLET_POWER;
		wave.setSegmentations(enemyDistance, enemyVelocity, lastEnemyVelocity);
		lastEnemyVelocity = enemyVelocity;
		wave.bearing = enemyAbsoluteBearing;
		setTurnGunRightRadians(Utils.normalRelativeAngle(enemyAbsoluteBearing - getGunHeadingRadians() + wave.mostVisitedBearingOffset()));
		setFire(wave.bulletPower);
		if (getEnergy() >= BULLET_POWER) {
			addCustomEvent(wave);
		}
		movement.onScannedRobot(e);
		setTurnRadarRightRadians(Utils.normalRelativeAngle(enemyAbsoluteBearing - getRadarHeadingRadians()) * 2);
	}
}

class GFTWave extends Condition {
	static Point2D targetLocation;

	double bulletPower;
	Point2D gunLocation;
	double bearing;
	double lateralDirection;

	private static final double MAX_DISTANCE = 1000;
	private static final int DISTANCE_INDEXES = 5;
	private static final int VELOCITY_INDEXES = 5;
	private static final int BINS = 25;
	private static final int MIDDLE_BIN = (BINS - 1) / 2;
	private static final double MAX_ESCAPE_ANGLE = 0.7;
	private static final double BIN_WIDTH = MAX_ESCAPE_ANGLE / (double)MIDDLE_BIN;
	
	private static int[][][][] statBuffers = new int[DISTANCE_INDEXES][VELOCITY_INDEXES][VELOCITY_INDEXES][BINS];

	private int[] buffer;
	private AdvancedRobot robot;
	private double distanceTraveled;
	
	GFTWave(AdvancedRobot _robot) {
		this.robot = _robot;
	}
	
	public boolean test() {
		advance();
		if (hasArrived()) {
			buffer[currentBin()]++;
			robot.removeCustomEvent(this);
		}
		return false;
	}

	double mostVisitedBearingOffset() {
		return (lateralDirection * BIN_WIDTH) * (mostVisitedBin() - MIDDLE_BIN);
	}
	
	void setSegmentations(double distance, double velocity, double lastVelocity) {
		int distanceIndex = (int)(distance / (MAX_DISTANCE / DISTANCE_INDEXES));
		int velocityIndex = (int)Math.abs(velocity / 2);
		int lastVelocityIndex = (int)Math.abs(lastVelocity / 2);
		buffer = statBuffers[distanceIndex][velocityIndex][lastVelocityIndex];
	}

	private void advance() {
		distanceTraveled += GFTUtils.bulletVelocity(bulletPower);
	}

	private boolean hasArrived() {
		return distanceTraveled > gunLocation.distance(targetLocation) - 18;
	}
	
	private int currentBin() {
		int bin = (int)Math.round(((Utils.normalRelativeAngle(GFTUtils.absoluteBearing(gunLocation, targetLocation) - bearing)) /
				(lateralDirection * BIN_WIDTH)) + MIDDLE_BIN);
		return GFTUtils.minMax(bin, 0, BINS - 1);
	}
	
	private int mostVisitedBin() {
		int mostVisited = MIDDLE_BIN;
		for (int i = 0; i < BINS; i++) {
			if (buffer[i] > buffer[mostVisited]) {
				mostVisited = i;
			}
		}
		return mostVisited;
	}	
}

class GFTUtils {
	static double bulletVelocity(double power) {
		return 20 - 3 * power;
	}
	
	static Point2D project(Point2D sourceLocation, double angle, double length) {
		return new Point2D.Double(sourceLocation.getX() + Math.sin(angle) * length,
				sourceLocation.getY() + Math.cos(angle) * length);
	}
	
	static double absoluteBearing(Point2D source, Point2D target) {
		return Math.atan2(target.getX() - source.getX(), target.getY() - source.getY());
	}

	static int sign(double v) {
		return v < 0 ? -1 : 1;
	}
	
	static int minMax(int v, int min, int max) {
		return Math.max(min, Math.min(max, v));
	}
}

class GFTMovement {
	private static final double BATTLE_FIELD_WIDTH = 800;
	private static final double BATTLE_FIELD_HEIGHT = 600;
	private static final double WALL_MARGIN = 18;
	private static final double MAX_TRIES = 125;
	private static final double REVERSE_TUNER = 0.421075;
	private static final double DEFAULT_EVASION = 1.2;
	private static final double WALL_BOUNCE_TUNER = 0.699484;

	private AdvancedRobot robot;
	private Rectangle2D fieldRectangle = new Rectangle2D.Double(WALL_MARGIN, WALL_MARGIN,
		BATTLE_FIELD_WIDTH - WALL_MARGIN * 2, BATTLE_FIELD_HEIGHT - WALL_MARGIN * 2);
	private double enemyFirePower = 3;
	private double direction = 0.4;

	GFTMovement(AdvancedRobot _robot) {
		this.robot = _robot;
	}
	
	public void onScannedRobot(ScannedRobotEvent e) {
		double enemyAbsoluteBearing = robot.getHeadingRadians() + e.getBearingRadians();
		double enemyDistance = e.getDistance();
		Point2D robotLocation = new Point2D.Double(robot.getX(), robot.getY());
		Point2D enemyLocation = GFTUtils.project(robotLocation, enemyAbsoluteBearing, enemyDistance);
		Point2D robotDestination;
		double tries = 0;
		while (!fieldRectangle.contains(robotDestination = GFTUtils.project(enemyLocation, enemyAbsoluteBearing + Math.PI + direction,
				enemyDistance * (DEFAULT_EVASION - tries / 100.0))) && tries < MAX_TRIES) {
			tries++;
		}
		if ((Math.random() < (GFTUtils.bulletVelocity(enemyFirePower) / REVERSE_TUNER) / enemyDistance ||
				tries > (enemyDistance / GFTUtils.bulletVelocity(enemyFirePower) / WALL_BOUNCE_TUNER))) {
			direction = -direction;
		}
		// Jamougha's cool way
		double angle = GFTUtils.absoluteBearing(robotLocation, robotDestination) - robot.getHeadingRadians();
		robot.setAhead(Math.cos(angle) * 100);
		robot.setTurnRightRadians(Math.tan(angle));
	}
}

What's next for your robot?

What other robot(s) is it based on?

Aristocles - both gun and movement


Comments, questions, feedback:

Way cool, PEZ! It might be cool to pair this with the BasicSurfer movement and post it to the rumble, just to get a sense of a "base line" for a functioning GuessFactor gun and WaveSurfing movement. (This seems a little more polished for what it is than BasicSurfer, though...) I'm curious to hear from some other people how clear this code seems to them, as I'm already very familiar with both GuessFactorTargeting, and the PEZ style of the GF code that is found here. This should certainly be linked from a few places, including the GuessFactorTargeting/Tutorial. (I'll do some of that myself sometime soon if nobody else does.) -- Voidious

Really nice! Just a quick question what is BIN_WIDTH? It seems to be

maxEscapeAngle/MIDDLE_BIN
, because the 0.7 is not really self explanatory (to me at least). Other than that it seems fine to me. -- Florent

I think using .7 instead of a true calculation is a "MiniBot-ism", as Math.asin(8/bulletvelocity(1.9)) is about .6 in radians. -- Voidious

No, it's not really minibot-ism. It's more of an approximation of the max escape angle that works for most bullet powers. But yeah, in a micro it saves spaces. I've changed the code above to include a MAX_ESCAPE_ANGLE constant. I hope that makes the code clearer. -- PEZ

Hi. I see, that you don't store your Waves in an ArrayList or Vector. Instead your GFTWave class exdends Condition. Could someone explain this point to me in more detail? By the way; I'm an absolute Greenhorn (Robocode and Java) but for some days i like to read the conversation of the godnesses from Robocode-Olymp. [Sir Drake]?

An AdvancedRobot lets you add CustomEvents like that - it probably uses an ArrayList or Vector internally. Every tick, Robocode automatically runs the test() method of each CustomEvent, so this setup is just making use of that capability of the RobocodeAPI to take care of the waves stuff. This setup is particularly useful in CodeSize-restricted bots, as well, as it saves you a little bit of code. -- Voidious

Hmm, it's late here, and re-reading my post, that might not have completely cleared things up for you =) It does give some links that might help clear things up, but certainly feel free to ask if it's still unclear. (This is a Tutorial type bot, after all!) Don't worry about being a Greenhorn, this site is the perfect place to be for a beginner to Robocode ;) -- Voidious

In this bot I do it for clarity. Instead of adding structural code to handle the waves I just use a built-in Robocode mechanism. As long as your somewhat familiar with Robocode and its custom events then it's less blur. Note however, that if you want complete control over the execution order you might want to manage the waves list yourself. That or figure out exactly when custom events are executed in respect to the rest of your code. In this bot though I opted for as little extra code as possible. -- PEZ

I have run a TargetingChallenge2K6 with GFTargetingBot 1.02, mainly to have a reflection of GresSuffurd against another simple GF-gun. The results are a bit less than I expected, although it is clearly stated that this bot is just a basic implementation without any advanced stuff. One thing is quite obvious, to hit a WaveSurfer you do need more than this. -- GrubbmGait
Name Author Gun BFly CC Chk Cig Cya DM FM Grb RMB Tig Total Comment
GFTargetingBot 1.02 PEZ GF 88.57 31.62 41.16 72.73 28.45 92.84 77.53 85.38 84.18 51.91 65.44

Indeed. In fact this bot is almost designed to miss a wavesurfer. Or, rather, any non-trivial WaveSurfer is designed to avoid it. Run a regular TargetingChallenge and you might see better performance from this bot. Is that a full TC2K6 run? If so, please pubish it on the appropriate results page. Good to have references like that. Especially if people base their bots on this code. -- PEZ

Results are published. I started an old TargetingChallenge, the results will be present in an hour or three. -- GrubbmGait
It has already finished, this gun is very comparable with Tityus's gun.
Name Author Type DT Asp TAOW Spar Cig Tron Fhqw HTTC Yng Funk Score
GFTargetingBot 1.02* PEZ GF 77.58 89.67 99.55 96.57 70.94 86.40 91.03 88.77 95.09 85.15 88.07

Just for grins, I DID pair this with BasicSurfer and the results were surprisingly good. It is BasicGFSurfer and ranks 47th in the RoboRumble as of 10-25-06. --Bayen

Yah, I did a merger too, and i'm trying to get mine to beat it. -- Chase-san

Okay, I'm trying to understand this code and have a couple of questions. In the GFTWave, when does the information from buffer get returned to statBuffers? It seems to me like it only takes data from statBuffers and never puts data back in. --Bayen

The magic is in this line from setSegmentation:

buffer = statBuffers[distanceIndex][velocityIndex][lastVelocityIndex];
After this, they 1D array "buffer" is set to point to one of the final dimensions of "statBuffers", so their cells point to the exact same data. This will save you from typing "statBuffers[index1][index2][index3]" every time you are accessing that data in that tick.

-- Voidious

Yah I had the same question, if Voidious' excellent explination wasn't enough you get my extremely confusing one, but one that comes with a visual represnetation...

Pointers 101: with visual aids

Here we have two different statBuffers, each with what
data they contain displayed respecibly below them.

  Main Class                  GFTWave
+-------------+           +-------------+
| statBuffers |           | statBuffers |
+-------------+           +-------------+
| 0           |           |             |
+-------------+           +-------------+

Notice how the statBuffers in GFTWave contain nothing, thats
considered a null state, or just null for short.

Now when you assign the statBuffers from the main class to
the statBuffers of the GFTWave your not actually copying the
data, no for that you would need to clone() it.

Now you have made a pointer! Congradulations.

  Main Class                  GFTWave
+-------------+           +-------------+
| statBuffers |           | statBuffers |
+-------------+  same as  +-------------+
| 0           | <-------- | 0           |
+-------------+    this   +-------------+

Now any modification you make to the data in the GTFWave
class will also take place in the main class aswell.

So when you assign or add or multiply or do whatever else to
the data in the GTFWave it also takes place in the Main class.

GFTWave:statBuffers = 1;

  Main Class                   GFTWave
+-------------+            +-------------+
| statBuffers |  actually  | statBuffers |
+-------------+ happens to +-------------+
| 1           | <--------- | 1           |
+-------------+    this    +-------------+

It also works in reverse. So anything you assign to the main class
will also happen to the data in the other class.

I explained all that just so I could make the ascii graphics. -- Chase-san


Robo Home | Changes | Preferences | AllPages
Edit text of this page | View other revisions
Last edited September 26, 2007 2:50 EST by Voidious (diff)
Search: