My talk proposal for OSCON 2014 was not accepted so I was attending as a booth brain. I liked this plan just fine since I wouldn't have to prepare much and could simply attend the conference and have fun showing off Cassandra / Spark demos on portacluster. Two weeks before the conference, I jumped on a hangout with a couple people from our events planning team. It went something like this:
Following that conversation, I looked at the source for many open source 2048 games, including the original. Since I needed to add swipe controls as well as gathering metrics from the game, I decided to spend an evening writing my own clone using D3.js to see how far I could get. I was able to push a nearly complete version of it to github the next day.
I actually had working code the previous evening, but it needed a name. I wasn't entirely convinced that writing it from scratch was the right way to go so I was still browsing Github looking for a good version to start with. One versions of the game I considered had a fun quote in its README.md, "The best game you'll ever hate." That made me think of rage comics, which led to the obvious conclusion: my clone needs to be called f7u12.
Besides using D3.js, f7u12 is different in other ways. The game itself is isolated in the f7u12.js file. It does not implement input or any game rules besides scoring. The whole thing is designed to be easy to wire up in various ways so I could quickly adapt to whatever equipment was available in our booth. I knew there would be a 40" touch-screen plasma and two iPads. We ended up being unable to use the touch functionality on the plasma, so the flexibility to change the game paid off big time. While the original intent was to offer a timed 2-player version of the game where you race for the highest score, we ended up with individual players on the iPads. It was fairly easy to implement either kind of game since the game was built to be used in a few different scenarios.
Here is the game. It is not reporting to any servers. Moves can be made with the arrow keys, by clicking on the d-pad to the right, or by using the arrow keys on your keyboard.
The main reason I decided to write the game from scratch was that I wanted to make it possible to capture everything that is happening, including some things beyond the obvious such as the amount of time between moves, direction choices, and the entire board state. As you'll see below, I didn't get all of the metrics available into the dashboard, but they are captured so they can be computed and displayed in future versions.
While many implementations use a 2-dimensional array, e.g. [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
, which
could be translated to something easier to serialize, but it's even easier with the way that f7u12 implements it
as a flat array, e.g. [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
, where the rows are laid out end-to-end. This
ends up being easy to pass around through 3 different programming languages (js, go, scala) and trivial to store in a
list type in Cassandra.
If you make a few moves in the game above, the data that gets sent to Cassandra should show up in the log area below (you should be able to horizontally scroll the div with the JSON in it).
In the live version of the game, every move is PUT to an HTTP backend that deserializes it then writes it to Cassandra in real time. I'll go over how that was built in part 2. The javascript code to submit the data looks like this:
Since the AJAX request doesn't block, it doesn't slow the game down at all. In order to show the real-time abilities of Cassandra, the dashboard displays current game state by reading from Cassandra via the same HTTP service that does the writes. There is no caching, so every read/write goes directly to Cassandra.
The blue grids at the bottom update a few milliseconds after every move made by a player. The two grids on the left were mirroring the iPads, while the third grid on the right represented the final state of games played by a silly procedure I got from StackOverflow , which was playing 1 game a second to provide some constant movement.
The numbers at the top of the dashboard show a leaderboard (topK) for humans and AI separately along with some other counts. These numbers are aggregated with a Spark job that I will go over in part 3 of this series.
At this point in the project I was feeling pretty good. I had a good idea of what the whole thing would look like
and how to put it together. The game seemed like the hard part at the time, but in reality it was much easier
than I expected because it is a simple game and I have a fair amount of experience with javascript and D3.js.
In the next post, I will cover how I wrote the HTTP backend application with
gocql and Cassandra. Following that
will be an overview of how the Spark job and dashboard were put together, so check
back soon!