Ironclad: Steam Legions – Clojure game development battle report

Posted: September 3, 2012 in Uncategorized
Tags: , ,

Over the weekend I did some work to get Ironclad running with Clojure 1.4 and uploaded it to Github as an open source project. I though that this would be a good milestone to reflect upon the work so far – and in particular I thought I’d share some of the learnings / interesting aspects of developing the game.

Clojure enthusiasts may find it interesting since it is a semi-complete game written in Clojure in a (mostly) functional style.

Functional programming for games

One of the interesting challenge in creating Ironclad was programming a game in a functional style. I actually wrote Ironclad to teach myself Clojure, so was determined to adopt a proper FP style for the core game engine in order to have a good learning experience.

This proved to have a few interesting implications:

  • The entire game state is immutable – this was a fun challenge, as games tend to have relatively complex nested data structures (including things like indexes for fast lookup of unit locations etc.). A significant part of the work in the core engine was making a library of functions that manipulate the game state and return a new immutable game state.
  • I used some Immutable Java data structures for performance. In particular, I wanted a fast map of (x,y) co-ordinate -> Object. I could have used Clojure data structures for this but my tests showed they were a little too slow, particularly when used for the AI routines which do a lot of seaching over these kinds of data structures. So I decided to roll my own. Worked well – Clojure makes it fairly easy to introduce new immutable data structures from the Java world.
  • Testing was delightfully easy – there is something about immutable data that just makes it very easy to test. Partly this is due to the general ease of “programming with values” but I think it also forces to structure your functions / code in a way that is inherently very easy to test.
  • Long parameter lists – I struggled a bit with this, and still don’t have a great solution. Because you are using immutable objects and don’t have reference graphs that you can easily traverse to discover context, you often have to pass a lot of arguments. Stuff like “(defn try-fire [ ability unit target game sx sy tx ty aps apcost] ….)”. I still haven’t found a totally satisfactory way to avoid or simplify this – you can do it with higher order functions but IMHO that makes the complexity of the code even worse. Alternatively you can compute all the “context” parameters over and over again but that can kill performance.

Overall I think that the use of FP in games is entirely reasonable and I’d count the Ironclad implementation as a reasonably decent success (at least for my first serious FP game project!). You just have to spend a bit of time bending your head around the new paradigm and adapting to its various advantages and disadvantages.

Prototype-based game objects

This was one of the aspects of the game architecture / codebase that I was most satified with. The combination of Clojure and prototype-based programming using Clojure maps/records as data structures is ideal for game development.

It made implementation of new units trivially easy. Here is the *entire* set of code required to implement the Riflemen, for example:

(make-unit-type
  {:name "Rifles"
   :value 40
   :size 2
   :description "...."
   :unit-type UNITTYPE_INFANTRY
   :move-type MOVETYPE_INFANTRY
   :ix 0
   :iy 1
   :can-capture true
   :apsmax 3
   :hpsmax 5
   :armour 60
   :abilities [
     (make-move-ability MOVETYPE_INFANTRY)
     (make-attack-ability {
        :name "0.303 Rifle"
        :attack-type ATTACKTYPE_SMALL
        :max-range 1
        :power 60
        :cost 1
        :consume-all-aps true})]
})

As you can see, it’s all very declarative and there is essentially zero boilerplate. What’s more, since the resulting data structure is immutable it not only serves as a decription (prototype) for units in the game it can also be coped directly into the appropriate game data structure to create a new unit. You have no mutability worries (which is frequently the case in OOP-based prototype systems that have references to mutable prototype objects).

Having this combination of conciseness, immutability  and declarative power makes it extremely easy to tweak the game balance and experiment with new unit features – work that is essential if you want to create a really great and balanced game.

You can even use this hack together new unit designs at runtime in the REPL – a nice way to test ideas without restarting the whole game to see the effect of each change.

Procedurally generated graphics

One of the interesting concepts in Ironclad was the use of the POVRay renderer to create the game graphics. I did this for a few reasons:

  • I’m a coder first and foremost. Drawing images is not my forte, but I can write code to generate images pretty fast…..
  • I wanted to be able to experiment with the visual look of the game, but not have to redraw new graphics every time. This was a massive benefit – the ability to experiment without significant rework is enormous in terms of unleashing creative potential.
  • You can generate a lot of permutations – in particular each unit requires at a minimum six different rotations and four colours for the different armies. Would be very tedious to do this all by hand, but in POVRay it’s just a simple macro…..

Overall I think I rather like this approach. It’s not a panacea – you still need a lot of attention to detail and hard work to get the graphics generation to look right. But it certainly is a feasible option for lone-coder game developers.

Ideally, I’d like to replace the dependency on POVRay with a Clojure-based raytracer. Apart from the fact that I think Clojure syntax would be idea for a raytracer scene description, it would open up some extra interesting options like having certain graphics elements be rendered on-the-fly during the execution of the game. Hopefully one day Enlight will develop to a point where it can play this role.

Embracing Swing

I made a conscious decision to use the Java-based Swing library as the graphics toolkit. Reasons were as follows:

  • The Java Swing library is mature and provided in all Java distributions (no extra dependencies)
  • Clojure’s ability to interoperate with Java is very good
  • 2D graphics in Swing was a good fit for a simple strategy game (I didn’t need anything like OpenGL)
  • I wanted to use standard UI components, but needed a custom look and feel to get the Steampunk effect. Swing seemed well suited to this task since it has a very flexible look-and-feel mechanism.
  • Miglayout is an excellent layout manager that takes away much of the pain for creating Java GUI layouts in code. This gave me some comfort that the layout code would be manageable.

As a result I build my own custom look and feel for Swing (steampunk-laf) which contains just about the minimum number of features you need to get a fully customised Swing GUI. This proved to be a more complex task than I thought as the logic of how Swing components behave can get quite complicated, and your custom look and feel needs to accommodate this. Nevertheless I think I got something reasonable decent working. It certainly is a big change from your classic business-oriented Swing GUI….

At the time I wrote Ironclad (2010) the Clojure Swing wrappers weren’t very mature, so I used Swing directly via Java interop. Still the code was pretty reasonable – I ended up with lots of stuff like:

(def bottom-button-panel
  (let [outer-panel (JPanel.)
        panel (JPanel.)]
    (doto outer-panel
      (.add panel))
    (doto panel
      (.setLayout (GridLayout. 1 3)))
    (.add panel (JPanel.))
    (.add panel button-next-turn)
    (.add panel button-exit)
    outer-panel))

The above is probably not ideal – it is a bit too imperative for my tastes and still requires a fair bit of fiddling with Swing internals. But it worked well enough for my purposes. If I was doing it again now I’d probably use Seesaw or something similar to give a more declarative, idiomatic Clojure wrapper to Swing code.

The future

I’m planning to continue to update Ironclad as I think it has the potential to develop into a pretty decent, well-rounded strategy game. Progress will probably be slow as it’s just a hobby project.

Comments
  1. Awesome! Writing a strategy game in the functional style using Clojure + Swing is *exactly* what I’ve been wanting to do. (In my case a turn-based Napoleonic wargame) I’m super-excited to see how you’ve implemented IronClad & I think you’ve just provided me with the necessary inspiration to get started. Thank you!

    • mikera7 says:

      No worries, glad you find it helpful! I think Clojure is a fun language for writing games, if a little mind-bending at times because of the need to shift away from the OOP mindset. I’ve been looking at using LWJGL from Clojure too, might be a better alternative to Swing for some types of game.

  2. Thomas says:

    hhhmmm…… Clojure and POVray, my two favourite tools. :)

    • mikera7 says:

      Yeah, I wanted to do something creative with POVRay for a few years. Learning Clojure was a chance to do that!

      As I mentioned though, I’d still prefer an embedded Clojure-based raytracer. I’ve written one before in C#, now just need to find the time to port the thing over to Clojure/Java

      • Thomas says:

        There is a very very simple clojure raytracer here:

        https://github.com/flitzwald/cray

        I have a lein compatible version on my hard disk somewhere that I have committed yet. Maybe a starting point?

        Thomas

      • mikera7 says:

        I had a look at cray but it wasn’t suitable: a) it isn’t architected in a way that you would ever get decent performance out and b) the maintainer isn’t working on it any more. Started on my own but haven’t got very far yet: https://github.com/mikera/enlight

      • Yves Parrays says:

        Is having a dependency towards POVRay that bad?
        If you want to use the raytracer online (i.e. during the execution of the game) maybe you’d want a hardware accelerated raytracer (don’t know if POVRay or YafRay provide that, anyway LWJGL+OpenGL can help you render to bitmaps). Have you built a Clojure EDSL to output the scene description you feed the raytracer with?
        Cause that’s maybe the best of both worlds: you describe your models in Clojure and you benefit from a full-fledged raytracer instead of having to re-roll your own. Plus it would certainly provide a very reusable solution.

      • mikera7 says:

        Nothing particularly bad about POVRay as a development dependency. But as a runtime dependency it’s somewhat problematic (complex to configure, tricky making portability work, non-trivial install / setup process etc.).

        For a Clojure/JVM based game ideally you want all of your runtime dependencies to be pure JVM code. Sometimes that isn’t possible of course (e.g. with LWJGL) but it certainly makes it much easier if you stick to pure Java libraries whenever you can.

  3. SI Hayakawa says:

    typo: “using Clojure maps/records as data structures is idea for game development” — I think it should be “ideal”, not “idea”.

  4. Marcus Magnusson says:

    Thanks for sharing! I’ve been meaning to start with some Clojure game project myself (going a little further than simple Tetris clones). POVray seems to be something I’ve been looking for for a very long time. Bye bye, Paint! ;)

    • mikera7 says:

      Yeah, generating graphics in code is much more fun than twiddling pixels. What sort of a game are you aiming to write?

      • Marcus Magnusson says:

        I will probably go for some Ikaruga:ish shmup, partly because I’ve got some ideas I want to try out, partly because I want the challange of lots of stuff happening at once. Will make sure to share if it turns into something interesting :)

      • mikera7 says:

        Cool stuff! Guess you’ll be wanting to use LWJGL? I was working up putting together a game-oriented CLojure LWJGL wrapper here: https://github.com/mikera/glaze

      • Marcus Magnusson says:

        Very likely, although I honestly haven’t worked very much with LWJGL before. Will be lots of new things :) Thanks, I’ll take a look at it!

  5. C.Keen says:

    Hi, I have just played ironclad via webstart but could not get the last two tutorial missions to work. I seem to be unable to move or shoot with any of my units. Is this a known issue? Thanks for the nice article too ;)

  6. Would be interesting to see it ported to ClojureScript to run natively in Browser.

  7. […] Ironclad: Steam Legions – Clojure game development battle report (the game on Github) […]

  8. Yves Parrays says:

    “Because you are using immutable objects and don’t have reference graphs that you can easily traverse to discover context, you often have to pass a lot of arguments.”

    I fail to see what you mean by “you don’t have refence graphs…”. If a lot of arguments are often needed together you can just wrap them up (first in a simple map, then in a dedicated record, when your code is more stable). Your sample contains names like “sx”, “sy”, that looks like coordinates that work together, you may have them packed.

    You can ever resort to use dynamic variables if the same argument is needed on its own in a big part of your code.

    • mikera7 says:

      By “reference graphs” I mean pointers encapsulated within objects. For example, in many games that allow inventories, each item will contain a (mutable) pointer to the player/creature that is carrying it. You can just do “(.remove item) and the remove method can remove itself from the parent’s mutable inventory.

      This doesn’t work in games that use immutable world state. It’s impossible to write (.remove item) because that function needs to build a new updated world state, and the item doesn’t have a pointer to the old world state. The method has insufficient information to do its job. You have to do something like (.remove universe player item), and perhaps even maintain a bunch of indexes as well.

      Also “wrapping up” objects for a single function call is expensive: it causes a *lot* of allocations. Most of the performance issues I have had with Clojure game development have been due to excessive allocations of temporary objects.

      • v.v.b. says:

        I can’t run it under debian. I’ve compile it, but at starting I get:
        no main manifest attribute, in ironclad-0.0.2-SNAPSHOT.jar
        can anybody help me? thanks.
        my email is vvb.backup ON rambler.ru

  9. […] Ironclad: Steam Legions […]

Leave a reply to Most interesting links of October ’12 « The Holy Java Cancel reply