Monday, September 8, 2008

Lessons learned from Code Ruler

Our instructor provided us with an assignment to implement an AI script for CodeRuler, a game that was created by IBM. You can find the official site here:

CodeRuler: http://www.alphaworks.ibm.com/tech/coderuler

The Premise
You are a ruler who has been given a castle, 10 peasants, and 10 knights. Peasants can capture land, garnering you points and faster reinforcements from your castle, but die to one swift blow from a knight. Knights can kill opposing peasants and knights and capture castles, and castles themselves will provide provide a new peasant or knight every few seconds, faster if you own more land. Points are gathered from killing the enemy and capturing their castles, as well as claiming land and having surviving units at the end of the game. Of course, the player with the highest number of points wins. The twist to this game, however, is that the player has no direct control over the units and must write an AI script in Java that will be used to control them.

The Implementation
For this assignment, we were assigned partners to work with and I had the pleasure of working with Tyler Wolff. The strategy that we decided to implement was to have the peasants capture land in a 'fill' fashion by moving up and down until it encounters a square already captured, then moving left and right one block and repeating the same process. The implementation for the peasants is actually quite simple since they can't do anything except this one function.

CodeRuler showing our 'fill' implementation for peasants

The meat of where the coding is, at least in my opinion, is writing an effective AI for the knights. Because the knights are the ones that have the biggest impact on the outcome of the game, we decided to focus most of our efforts on writing an effective strategy for them. What we ultimately decided on was to eliminate the biggest threat first - the enemy knights, and then take care of the other units only when there are no knights to oppose ours. To that end, we try to 'gang up' on a single knight by picking one that's closest to one of our knights and then sending all of our knights to kill that single one. Unlike peasants, knights have health and it takes several attacks to bring one down, so focusing the attacks should prove more effective than randomly selecting targets.

However, there are some other factors to consider. Since only one unit can occupy any given space at any time, a situation can occur where all the knights try to go towards one enemy unit but get stuck behind each other, forming a line that's easily defeated. This is because when the knights try to move to a square already occupied by a friendly knight, they can't do so and therefore stop in their tracks, essentially wasting a turn. The fix for that was to make them turn 45 degrees to the left or right and attempt to head in that direction instead, moving around the knight rather than bumping into him repeatedly. This code worked quite well and improved our results drastically. We even took it one step further and added an additional check to see if there is an enemy in the new direction and to attack it if there is. However, the code only performs 2 checks and if both spaces are occupied by friendly units, the knight will not move or attack.

If all the enemy knights are dead or if a castle happens to be closer than the closest enemy knight, the knights will attempt to capture the castle. The process of capturing a castle is the same as capturing land, except only a knight can do it. After all the enemy knights and castles are killed and captured, the knights will then go after the closest peasant. Our code is written so that if there is already a knight chasing after a peasant, another knight will not join the chase and will instead pick a random direction to move in until it finds a peasant that is not being pursued by another knight. This strategy works best if the knights are spread out, as each knight can chase down a different peasant, but does not work so well if they are bunched together because they will all see one peasant as the closest one, but only one knight will chase after it while the rest move in random directions. However, at this point the enemy has no castles and no knights, so the player has essentially won and killing the peasants slowly or quickly has little bearing on the final outcome.

Playtesting
So all this is good in theory, but how does it hold up against other AIs? Although we couldn't test our AI against the other groups that also worked on this assignment, we did manage to test it with the built-in AIs and the results were impressive:

Our AI vs. Migrant Ruler
Trial 1: 864 vs. 0
Trial 2: 812 vs. 0
Trial 3: 639 vs. 0

Our AI vs. Gang Up Ruler
Trial 1: 712 vs. 84
Trial 2: 739 vs. 133
Trial 3: 809 vs. 60

Our AI vs. Smart Split Up Ruler
Trial 1: 788 vs. 70
Trial 2: 797 vs. 86
Trial 3: 801 vs. 58

Our strategy was very effective in eliminating the enemy knights and our AI can easily win any one-on-one match. We even tried it with a 6-player free-for-all and our AI can win up to 70% of the games. There is only one potential problem; CodeRuler imposes a time limit on how long it takes code to run, a mere 0.5 seconds every turn. Because our code contains a large amount of loops and checks, it can reach this limit, especially when the knights are going after the peasants. When this limit is exceeded, CodeRuler will stop the AI script from running, although all the other AIs will continue to run, leading to a situation where your units sit there and don't do anything at all while the enemy wipes out your forces with impunity. This would be a good way to impose a cap on the amount of logic that people can program in and would strike a good balance between complexity and optimizations, except for one problem: it is completely dependent on how fast the computer is that CodeRuler is being run on. Someone with a faster system will be able to implement more complex code than someone with a slower computer. In my opinion, there needs to be a better way to impose this limit because even amongst our small class of 20 students, there is a big variance between computer speeds.

Lessons Learned
Through this experience, I feel like I've learned a great deal about programming itself. Eclipse surprised me a number of times, especially when it pointed out which variables I haven't used, and going from using a text editor (albeit with syntax coloring) to an IDE has been an enlightening experience. CodeRuler is, I believe, a really great alternative to writing simple Java programs because it presents a clear and fun goal that can be constantly improved upon. If the processor speed issue is fixed, it has the potential to be the next "Hello world" program. Lastly, working with Tyler has been a very good experience. Usually when I get into groups, I tend to try and do everything by myself because my partner almost always ends up being incompetent or lazy and I find it much more reassuring to finish everything by myself so I'm not placing my grades in the hands of something I don't trust. Tyler, on the other hand, has shown me that if people do get together and pull their weight, they can yield a product that is more than the sum of its parts.

You can obtain a copy of our code and our Javadoc from here:

http://www2.hawaii.edu/~wolfft/tylerwolff-daniel.f.tian.zip

1 comment:

Meta said...

Hi! Nice job btw :)

Could you please remove the source code from there? :)

Because we are doing this as a gradeable work that is one of the most important this semester at our university and by having your code here public :

1- More people will get the ban hammer from MOSS (source code copying detection system)
2- People will think less and google more :P

Thanks :D