Rozu released the source to his breakthrough melee bot, HawkOnFire, when he saw that people were genuinely interested in it some time ago. Since then, I think it's been on everyone's todo list to take a look at it. You can view it here.
It seems that Rozu took some time to edit and fix up the code before uploading it here, and for the benefit of clarity, it compiles to be a bit larger than the barely-micro size HawkOnFire normally is. That having been said, with some analysis and tweaking, I got HawkOnFireOS under 700 bytes with no loss of functionality or performance that I can see.
HawkOnFire is made up of 2 classes:
- The main robot class.
- the microEnemy class, which contains the location, energy, and whether or not the enemy is alive. These are stored in a Hashtable in the robot class.
The primary magic in HawkOnFire lives in the evaluate() and doMovementAndGun() methods. run() doesn't do much besides set up and call these methods, and onScannedRobot() just updates the enemies and handles target selection.
There are a few typical utility functions:
- calcPoint(), which projects a point some distance away from a base location at some angle.
- calcAngle(), which finds the absolute bearing from one point to another.
HawkOnFire also overrides the onRobotDeath() method to mark enemies as dead.
Most of these are pretty self-explanatory, but the Points declared should be explained:
- nextDestination is the point that HawkOnFire is trying to get to.
- lastPosition is the point that HawkOnFire is coming from last.
- myPos is where HawkOnFire is now.
Spins the radar in melee, Infinity Lock in 1-on-1.
Not too exciting. The enemy's position is set in onScannedRobot(), and lines 54-57 turn the gun and fire.
Contrary to rumors, HawkOnFire does not always fire power-3 bullets ‒ it's a little more aggressive than a lot of small melee bots as far as firepower is concerned, but not as aggressive as bots with a dependable gun. One feature that's worth pointing out is that
target.energy / 3 is in there, which is more than enough to kill the target at that point. HawkOnFire doesn't want a low-energy target to get lucky after being hit. Also, HawkOnFire always tries to target the closest living enemy, and likes to have its gun turned, and never fires when its energy is under 1.
HawkOnFire is a textbook example of Minimum Risk Movement. The point-generating function generates 200 points at random between 100 and 300 pixels away but no more than 80% of the distance to its current target (to avoid hitting other robots as much).
The evaluate() method is HawkOnFire's risk function, except for one part ‒ the addLast variable that is passed in as a parameter is precomputed for consistency so that the same value is used when comparing for every point. addLast is set to
1 - Math.rint(Math.pow(Math.random(), getOthers())), which makes it 1 most of the time with more bots on the field and 0 about half of the time in 1-on-1. This variable determines whether lastPosition is used in the risk calculation.
Which leads us into evaluate() itself. Basically, after starting out the risk as addLast times an anti-gravity function on lastPosition, HawkOnFire iterates through the enemies, and for each live one, adds the proposed risk to the total risk (called eval). The formula is very simple and elegant (and I don't think I'm exaggerating to say that it's a breakthrough in and of itself) ‒ simply
energyRatio * (1 + perpendicularity) / distanceSq.
- energyRatio is the enemy's energy / HawkOnFire's energy (but no more than 2) ‒ this makes enemies with higher energy more "dangerous".
- perpendicularity is the absolute value of the cosine of the difference between the angle from the point to HawkOnFire's current location and the angle from the point to the enemy. This is 0 when the point is at a right angle and 1 if the point is directly toward or away from the enemy.
- distanceSq is the distance from the point to the enemy.
Once you understand this formula, you pretty much understand HawkOnFire.