Sunday, April 22, 2012

Orc Battle In Clojure

The goal of this is to explain how I implemented Orc Battle in Clojure and the changes I made. Part of my intent with implementing the program in Clojure was to rewrite it in a functional style. If you are interested in a straight port of the code, that can be found here: Orc Battle Online. As a bonus, this also includes a web interface using Ring and Hiccup.

The first big change is that this version does not make use of any global variables. A key concept of functional programming is that calling a function does not have any side effects. Any time we modify a value, such as attacking the player and decreasing his health, we cannot modify any external state. Rather we pass in the player as a parameter to the function, and generate a new object with this modified state and use the returned value later on in the program. Since we no longer have the global variables keeping track of the players stats, we need to create a structure to keep track of this:

Not only are we keeping track of the current stats, we have the original values. I plan on adding increasing rounds of difficulty and the ability of the player to level up.

For our first function, lets create one that damages a player's attribute by some amount. So given the player, attribute and damage we want to return a new player structure with the given attribute altered. We can use the assoc function, which takes a hash-map, the key and the new value. This function will return a new hash-map where everything is the same except for the specified key, which now has the new value.

As you can see above, after calling hit player we have a new object returned with the updated values, but we do not have to worry about our original player being changed. As a result testing become easier. You can reuse the same object over and over while testing a function, since most functions do not have side effects, so only the returned results matter.

The next piece is dealing with the monster methods and dispatching on type. In clojure there are several approaches to dealing with inheritance. One approach uses defrecord and protocols. I tried that way first, but with little experience using it, I found the resulting code less clear. An alternate approach that I used was multi methods. The main compents of a multimethod are the method name and the dispatch function. Since a key (e.g. :some-key) can act as a function, that means you can dispatch the method based on the value of a given key or set of keys of a map object. So in this case we have the following method signatures and implementations:

All of the multi-methods are being dispatched on the type property of the maps. We then define the type hierarchy, with monster being the base type and both orc and hydra deriving from that. So now we have the polymorphism we'd expect. In each of the orc and hydra objects the type property is defined. This value will be used for dispatching. We have two implementations of the monster-show method. One defined for a generic monster and one specialized to the hydra. Since no specific orc defined method exists and orc is derived from monster, the generic version will be used when called with an orc. When called with a hydra we get the specialized hydra version.

We also need some addition methods to help with I/O. When using swank in emacs, the regular read-line method does not work. Fortunately swank-clojure provides a method to work around this problem. Also, read-line returns a string with a newline character so we need a method to parse the integer.

The last major change was dealing with the monster attacks and player attacks. I struggled with this a little, since this step is really where most of the regular state change was occuring. My first attempt used loop/recur. While it worked, the resulting code was not idiomatic and was not using the sequence library to process the code. With some help (Stackoverflow question) I was able to use a solution which made better use of the sequence library using reduce. Similarly I was able to use reduce for the player attacks, with the list being n copies of the player where n is the number of attacks the player has.

The one place where I did stick with a loop/recur was with the main game loop. Since I am not specifically using a data structure to loop on, I am uncertain if there is a better way to implement this.

The full code can be found here along with the original common lisp code.

No comments: