A Competitive Robocode Robot

For anyone keeping score, I've recently written about Robocode, JUnit, and Ant Build Systems. Now it is time to put them all together into one project. On October 11th, my robocode robot will compete against other robots for the glory of victory. But what's really important is what we've learned along the way (insert snide comment here). So let's dive in.

Robocode
You'll recall that robots have three behaviors: movement, targeting, and firing. 

I experimented with a robot that moves less often to save energy and one that moves more often to avoid bullets. In my tests, moving on each turn was clearly better than only moving after being hit by an enemy. I've heard of some other tactics that move only when they detect an enemy bullet being fired, which would give them the best of both worlds. But my movement strategy is much more simple. Each turn, I move forward 200 pixels. If I hit a wall, I move in the opposite direction. If I am hit by an enemy bullet, I turn 90 degrees and run 250 pixels forward. A sophisticated robot could pick up on this pattern, so I am just hoping that there are no robocode experts hiding among my tournament competitors. 

My firing method is also fairly simple. When I detect an enemy, I shoot. I calculate the power to fire with based on the enemy's remaining energy and whether or not they are moving. The goal is to only use as much energy as is necessary to kill the target, but also adjust the power down when the target is moving because there is less chance of hitting them.

I spent most of my time working on targeting. My strategy is to use the enemy's speed and heading to calculate where they will be by the time the bullet reaches them. The following two screenshots show my robot's bullet (white) leading the enemy, and then impacting. This strategy involves using trigonometry to determine the enemy's current x and y coordinates, then estimate their future coordinates based on how fast the bullet can reach them. There was one difficulty with this strategy. I need to estimate how long it will take my bullet to reach the enemy in order to know how far to lead them. But I need to know the position to shoot the bullet at before I can calculate how long it will take to reach that point. The amount of time it will take the bullet is required to determine the final position, but the final position is required to calculate how long it will take the bullet to get there. I use iteration to reduce error in this chicken-and-egg problem: I start by determining how long it will take the bullet to reach the enemy's original position and calculate the final position based on that. If the distance to the final position is too different than the distance to the original, I use the estimated final position to recalculate the time it will take the bullet. I do this iteration until the difference is acceptable, or a maximum of five times.
Of course, improving linear targeting creates a weakness against robots that don't travel in straight lines. If my robot loses a round, I switch to a slightly simpler algorithm.




JUnit
I created simple acceptance tests against seven sample robots. My robot is able to beat Corners, Crazy Fire, RamFire, Tracker, and Walls in 10 out of 10 rounds. SpinBot is difficult for my algorithm since it doesn't move in straight lines, and I can only beat it in 5 out of 10 rounds. Being able to quickly and deterministically test against all of these robots was helpful in honing my strategy. After each code change I could run the acceptance tests to see the impact of the change. This sometimes revealed drastic effects of a change I thought seemed trivial and wouldn't have bothered testing any other way, like the difference between using turnRight and setTurnRight after being hit by a bullet. 

I also created unit tests to ensure that my methods to calculate position, angles, and fire power were returning the correct values. I created a group of asserts for each of the method's parameters, varying the value of the parameter to cover all of the equivalence classes that the program might encounter. For instance, calculating the heading to fire at relies on the enemy's current heading, speed, and distance from me, and on my bullet power and position. By varying each of these parameters one at a time I could check that the algorithms were behaving as planned under all conditions.

Finally, I created a behavioral test. There are two reasons to use behavioral tests in addition to unit tests. First of all, they put all of the pieces of my unit tests together. It may be possible that each of my individual methods are working correctly, but I use the returned values in the wrong way. Secondly, it uses a real robocode battle to do the testing in case I missed an equivalence class in my unit tests. My behavioral test calculates the accuracy of my targeting method and considered it to pass if over half of the shots hit the enemy. I used this to confirm that my iterative algorithm described above improved targeting accuracy from 26% to the 50% required to pass the test.

Ant Build System
The build tools were very convenient, but they did require me to change how some of my code was structured. For example, in the conditional below, jacoco marked the highlighted line as not covered. When I removed "ydist > 0" from the check, the warning disappeared. Since the previous conditions cover all cases where ydist would be greater than zero this check was not strictly required, but I believe it makes the code easier to read. PMD required similar refactoring in order to pass.


As I mentioned, I ran the junit scripts often throughout the development process, but I didn't think to do the same for checkstyle, PMD, and findbugs. I wish I had. When I finally did run them, I realized I forgot to put periods after the first sentence of all of my comments. Spending five minutes straight adding periods to comments was frustrating, to say the least. It would have been better to catch problems like this the first time I make the mistake, which would remind me to do things correctly throughout the rest of the project.
To be honest I found the build tools annoying. I expect they take some time to get used to and eventually the things I find strange now will become second nature.


Anyway, I think that is enough for today. Hopefully all my work will pay off in the big robocode tournament.

Comments

Popular posts from this blog

Working with Esper: Problems and Solutions

Writing and Testing FizzBuzz in Eclipse

WattDepot Katas