Clojure Cup 2014 Post Mortem
I participated in Clojure Cup again this year, making it two years running. I worked on a team with hlatki on a simple in-browser number game slightly influenced by the style of 2048 called Number Chain. I've migrated the final source to be under my Github profile and a more recent version of the game can be found here.
The Idea
I had been interested in making a game based on numbers since 2048 became popular earlier this year. The simplicity was appealing, and for a 48 hour competition with a focus on the browser, it made sense to try to build something like the original game 2048 browser game which worked great on mobile devices, but using ClojureScript as the core technology.
What Went Well
The Clojure Cup sponsors did a great job of providing the participants with development resources. We were provided servers from CloudSigma, a private Github repository to host the code, Flowdock access for team communication, among many other things. I call out the three above as they were the three provided services we used for developing Number Chain.
In addition to the provided services from Clojure Cup, we used a Trello board as a way to organize tasks and coordinate our work. It was very effective. Before the cup began, I populated the board with what logical tasks I could think of, haltki setup our Flowdock, and I setup a CloudSigma Image. I created a skeleton repository based off of the Perfection skeleton project on Github, which would allow for quick development of our application.
Since React.js has been such a popular library for web development this year, we decided that we should use one of the ClojureScript libraries that wraps it's functionality. We narrowed it down to Reagent and Om. In the end we chose Reagent for it's lower learning curve.
Reagent ended up being a great choice. All you really need to do to get started with it is refer it's atom
into your namespace and use it when creating your application state. You then simply write functions that render the various pieces of the DOM (using hiccup style templates), and when anything in your application mutates the state of your atoms, the components that are using those atoms derefenced values are re-rendered.
A good example of this is the code used to implement the score, found in the score namespace:
(ns number-chain.score
(:require [reagent.core :refer [atom]]
[clojure.string :as string]))
(def score (atom 0))
...
(defn score-component
"Reagent component for the current score and high score."
[]
[:div
[:div.score (str "Score " @score)]
[:div.high-score (str "High Score " @high-score)]])
As you can see, the score-component dereferences the score
atom that is defined at the top of the namespace. In the core namespace of the application the score-component is mounted like so:
(reagent/render-component [score-component] (get-element-by-id "score"))
With the (get-element-by-id "score")
returning a DOM element for the component to be rendered into, which in this case is a div. With that done, anything in the application that modifies the score
atom will cause the score-component
to be re-rendered automatically.
Overall, Reagent fit our needs well for Number Chain as the scope of the project was relatively small. I can see a stronger argument for Om for projects that are going to be significantly more complex, but for something quick and simple Reagent really shines, in fact it ships default with the Luminus Clojure Web Framework.
What Didn't Go Well
For the portions of the competition that didn't go well, one thing really sticks out: CSS. I've always avoided CSS as for the most part I've not done a significant amount of front-end web development. That really bit me for this project. hlatki has more experience in this regard, and ended up getting the look and feel of Number Chain to where it was. Despite this, it still took a significant amount of time for both of us, time that could have been better spent working on some of the stretch goals we had for the application.
Another pain point related to the front end web development was timing quirks and events. The simple case of clicking just worked, and that is how we trigger a selection toggle on the cells when you use your mouse:
(defn small-cell
"Takes a single value from the :numbers app-state value and produces the cell
that represents that number. Properly sets the bg color and data-id tag
fields."
[{:keys [value id]}]
[div-grid-col {:style {:border "1px solid #d3d3d3"
:background (if ((:selected @app-state) id)
(:selection-color @app-state)
"#009bcc")}
:data-id id
:id (str "grid-" id)
:on-click toggle-selected}
value])
As you can see, this is embedded into the actual HTML generated when the reagent component is rendered, so it just works. Unfortunately, for a mobile based environment we have to add the touch listener in raw Javascript, which we did with this function:
(defn attach-touch-listeners!
"Add the touch listeners to all of the grid-cell divs in our game. Needs
to happen after each rendering of the game grid."
[]
(let [els (get-elements-by-class-name "grid-cell")
size (.-length els)]
(doseq [e (range size)]
(.addEventListener (aget els e) "touchstart" toggle-selected))))
;; Actually called via:
(js/setTimeout #(attach-touch-listeners!) 50)
The timeout above was needed to handle issues we saw due to a delay in when the applications state was modified to start the game and when the actual grid cells were rendered. Without the delay, there were no element to attach the touch listeners to. Unfortunately, it took a bit of time to discover this, and then fix it.
In the End...
We had a great time. It was a nice way to spend a weekend working on something for fun, trying out new things, and having something (semi-)polished to show off. I look forward to the next Clojure Cup! In the mean time I'm going to be looking at CSS a bit more, in addition to all the exciting things happening in the Clojure/ClojureScript space.