I am slowly writing a clone of a game I made during college with some friends. This clone is called Orion’s Belt BattleGrounds and is open source. I wrote the first version of the engine in Clojure and was waiting for the right time to port it to JavaScript. When Clojure 1.7.0 was released, it brought a new cljc extension that enables us to write common Clojure code. This means that the same code can be run as Clojure or as ClojureScript.

This was my chance of having 4K LOC running on both the JVM and JavaScript. I made the engine with no dependencies. It’s only pure logic and I only depend on some math utilities and test frameworks. So everything was expected to work. Even so, I was a bit afraid of spending time in this, I was afraid to get blocked by something not implemented on ClojureScript or some platform limitation.

Game Overview

The game is a chess-like board game. There are custom units, and every unit has different traits and powers and can move differently. The player has 6 action points to spend on its turn and each unit has its own movement cost.

The engine I wrote was able to process the majority of the original’s game rules. As an addition, I also wrote a simple artificial intelligent logic that was able to play the game.

Booting the tools

I’d say that my biggest difficulty was tooling. My objective was to have the full test suite running on Clojure and ClojureScript. I started by moving a file that had no dependencies to cljc and also it’s test. I am using lein and the first problem I’ve encountered was that lein test can’t find tests to run. It can load namespaces on cljc, but it can’t find the tests and I don’t know why. I also tried boot, but also had some bumps so I decided to stay with lein.

The only way I could run my cljc tests was specifying the namespaces like lein test obb-rules.unit-test. I wrote a bash script for this and used this to run them all for a while.

However, I also wanted to run exactly the same tests on ClojureScript. I found out that it’s idiomatic on ClojureScript to have a specific test runner file that requires the tests interfaces and runs them. I liked it and ended up creating a runner for clj and another for cljs. Both runners include a file that requires all test namespaces. This means that whenever I add a new test, I need to add it to this file. The bright side of this is that I now have full control on how to run my tests, and can easilly create an app that runs them.

I did had some bumps configuring ClojureScript on lein. The best way I found to manage all these different ways to build the same code was with one lein profile per scenario. And I have three scenarios (clj, cljs-node, cljs-browser):

This is all in a special script that runs everything. I can be working just on the Clojure part or just the ClojureScript part, and when I push to origin Travis will test all the scenarios.

Migrating the Code

When I had a way to properly run the tests on all those scenarios, I started migrating file by file. It was actually very easy. I just changed the extension to cljc on the source and on the test and check if it worked.

I only had to change the requires on the test namespaces to something like:

There must be some convincing arguments on why we have a clojure.test and a specific cljs.test. The truth is that I had to change just this on a lot of files.

Sometimes cljs would give me some weird errors. It was always my fault, but it made me spend more time than I should on them. Another thing unexpected on the cljs compiler, was that it is less permissive than the clojure one. It fails if I have the same require twice on a namespace, and it warns me when I have 2 tests with the same name.

I did had to write specific platform core, mainly for my math utils and some platform utils. These namespaces have functions to parse numbers, get environment variables, and so on. They are smaller than I would expect.

test.check

I have some tests relying on test.check and it was with surprise that I saw that ClojureScript was supported by them. Again, the migration was flawless. I changed the files to cljc, I runed the tests and everything passed. Sometimes I changed the tests to fail just to be sure that all this was running.

Again, the only trouble I had was with the requires:

Don’t know if I can find a way to cut down all this boilerplate.

It’s quite nice to setup test.check to run 10K scenarios per test and see them all succeeding on all platforms. And my test.check tests are heavy, they mainly test the AI: create a game with random generated units, deploy the units, play turns.

Performance

When I had all tests passing, I started comparing JVM against Nodejs. Well, these tests are CPU heavy and I expected that the JVM would be faster. Even so I confess that I was pretty curious. These are the results for 10k runs on test.check:

Note that I didn’t do any tune ups and no warm ups and just measured it with the time macro.

What’s next?

Now I have all the engine working on the client side. I will now start to write a simple demo that just allows a player to play against the AI.

I’ll write more about developing it on twitter.

EDIT: You can now play the browser version.