Tuesday, October 11, 2011

A Prime Example of What It Takes to Not Win at the Robocode Tournament

1. Overview
We have all been recently promoted to a belt of the next higher level and have now been asked to compete in the once-per-course Robocode Tournament. Am I strong enough? Have I trained enough? Do I have what it takes? Well, it was time to take one step at a time and see for myself.

To start off, I used the Robocode Katas Assignment as a foundation upon which to build my competitive robot. Notably, I made use of HitWallEvent from Position02, movement from Position04 and Position05, and firing tactics from Boom03 and Boom04. Of course, bunching all this code up together in a single robot is not ideal. So I then focused my efforts on beating one sample robot at a time.

In an ideal world, my main objective would be to win the Robocode Tournament if I had had more time to work on this. However, lack of extra time was indeed the case, and so, my main objective was set to the minimal requirements: to defeat the eight sample robots, namely, SittingDuck, Walls, RamFire, SpinBot, Crazy, Fire, Corners, and Tracker.

2. Design
A. Movement
My robot's basic movement is that it always moves to the center at the start of a round, so as to maximize its chances of getting closer to the enemy robot, since the closer it is, the greater its firepower. The only other time my robot moves is when it dodges (moves ahead by 100 pixels), if the following two conditions are met: its distance from the enemy is greater than 200 and its energy is less than 40.

B. Targeting
In the run method, the radar constantly turns in the while true loop (but this only happens if the robot hasn't chosen a target yet). Targeting is calculated with a simple equation in the method trackTactic: my robot's heading minus its gun's heading plus the enemy's bearing. The gun should only turn towards the enemy if it is not already pointing at said enemy. For some reason, however, my robot still turns its gun at times even though it has been constantly firing at the enemy who has been standing in the same location.

C. Firing
An enemy robot is fired at once it is detected. My robot's firepower has strengths 1, 2, and 3; the closer the enemy is, the weaker the firepower, and the farther the enemy is, the stronger the firepower.

Beating one robot at a time:

R1. SittingDuck
A no-brainer. This robot doesn't do anything, so there's no strategy required in beating it.

R2. Fire
My code for beating Fire certainly isn't the most efficient implementation. But it works. Fire helped the logic in constructing the onHitByBullet method, which triggers a HitByBulletEvent if the robot is hit by a bullet. The bearing of the bullet is the direction in degrees relative to my robot's heading. Basically, if the robot is not facing the bullet dead on, just move forward by a few pixels. But of course, if the bullet is hitting the robot in the face, turn right by a few degrees before moving forward.

R3. RamFire
While the Fire sample robot centers on the onHitByBullet method, RamFire centers on the onHitRobot method, since that is exactly what RamFire does: hits the enemy robot. I tried firing twice with a maximum power of 3 and to my surprise, it beat RamFire a few times. However, I wasn't satisfied with two lines of code, so I had my robot fire only if its energy was greater than or equal to the enemy's. If my robot's energy was less, then it should back away. However, this tactic did not work well at all, since the extra time taken in moving backwards allowed RamFire to move forward in the same amount, corner my robot into a wall, and beat it to a pulp each time. In the end, I stuck with two lines of code that at least beat RamFire sometimes instead of never.

R4. Crazy
Crazy moves around a whole lot, which is good for evasion but not exactly a great evasion tactic, especially since my robot was able to win most of the time against it. I used a combination of two robot katas (namely, Boom03 and Boom04) to win against Crazy. The overall strategy is, the further away Crazy is, it's still good to try and shoot at it but with minimal firepower, since the majority of the time, my robot missed like crazy. And of course, stronger firepower was the way to go when crazy was nearer to my robot; it was the only time for redemption. Even though missing was still a high percentage here, getting in a few good hits was worth it.

R5. Corners
The tactics that Corners uses are simple but not quite strategic enough: pick any of the four corners, stay there, and shoot at the enemy. To beat Corners, I focused on the onHitByBullet method, since my robot seemed to be getting hit quite a bit. I noticed, however, that Corners only uses a firepower of 1. So instead of dodging all the time whenever my robot got hit by a bullet, I only made it dodge if its energy was less than 50 (half of the initial energy, which is 100) or if the bullet that hit it had a firepower greater than 1. Again, this is not the best strategy, but it beat Corners on more than one occasion.

R6. Tracker
Unfortunately, my onHitByBullet method worked for Corners but not for Tracker. Tracker follows the enemy and fires with maximum firepower of 3 at all times. Because Tracker always ended up close to my robot. This meant that even if my robot had more health and fired with maximum firepower at Tracker, Tracker would win because my robot would begin to dodge if its health was less than 50. This wasn't a good idea at all, as it always made my robot lose. So in the end, I created a boolean variable called "dodge" and initially set it to false, only to be true under certain conditions. In Tracker's case, dodge is set to true only if the distance between Tracker and my robot is greater than 200 pixels and my robot's energy is less than 40. But what really got my robot to beat Tracker was to change the requirement for maximum firepower of 3. By being more lenient on the requirement (the distance between my robot and the enemy robot is less than or equal to 300 pixels instead of 100), my robot had the upper hand on Tracker.

R7. Walls
I tried what I could against Walls, including using an onBulletMissed method to increase an integer count on the number of missed bullets my robot would fire. If this was above a certain number in the onScannedRobot method, then this would trigger a method to move my robot to a corner of the map. Unfortunately, before my robot ever got close enough to block Walls within its path, it often became an open target for Walls. I ended up having to delete anything to do with Walls. At this point, I don't think my robot can ever beat walls.

R8. SpinBot
At this point, my robot had enough basic skills to be capable of beating SpinBot on very minor occasions. The spawning location at the start of a round was one determining factor, as well as my robot's heading relative to SpinBot's overall position as he travels in circles. But in short, SpinBot was the victor.

3. Results
I used the two JUnit test classes for Corners and Fire to run 100 battles against each sample robot and calculated what percentage of the battles my robot won.

Sample robots that my robot can reliably beat:
- SittingDuck (100/100, 100%)
- Crazy (75/100, 75%)
- Fire (85/100, 85%)
- Corners (97/100, 97%)
- Tracker (85/100, 85%)
- RamFire (74/100, 74%)

Sample robots that my robot cannot reliably beat:
- Walls (0/100, 0%)
- SpinBot (7/100, 7%)

The detailed explanation of why my design worked for the majority and yet didn't quite work for some is located under the "Design" section, entitled "Beating one robot at a time."

In order to do better, I would improve my design by examining even more cases and setting more boolean variables or running counts on certain items, such as the number of times its bullets miss or the number of times it gets hit by a bullet. If the robot is in a certain state, then a particular tactic would be used instead.

4. Testing
I wrote two acceptance tests, two behavioral tests, and two unit test. The two acceptance tests simply test whether or not my robot beats Corners and Fire. The two behavioral tests test my robots movement and firing (there wasn't much strategy to targeting, so I left that part out). Lastly, the three unit tests checked to see whether or not my robot has dodged at least once in an entire battle and whether or not my robot has fired twice upon colliding into an enemy robot. Both JaCoCo and JUnit run successfully.

5. Lessons Learned
In future, instead of focusing all my energy into coding for one-on-one matches, I would develop strategies for matches consisting of more than two robots. In regards to software engineering, I have learned (from this project) that it is helpful to write about what you do along the way instead of writing about it after you are finished. This also minimizes error. From a software development perspective, I would definitely give myself more time to work on the project.

No comments:

Post a Comment