Nov 14 2013

Using GoInstant at Node Knockout 2013

image

Last weekend, I competed in Node Knockout 2013 along with some fellow MojoTech-ers (Sam Saccone and Craig Jolicoeur) and designer Marissa Epstein.

Our entry was game about a balloon named Zoom trying to fly to the moon, and you can download it for mac, or just play it online.

We are proud of the level of polish we were able to achieve in such a short amount of time, as well as how legitimately fun the final product was. If you love it too (or even just like it), you can still vote for us below.

The weekend as a whole, was a ton of fun, and in order to keep stress low and speed-up development, we used a number of different tools, including crafty for rendering most of the game, node-webkit to create a native application to distribute, and roots to streamline our local environment and deploy process.

We also knew from the beginning that we wanted the ability to view game replays from the high score screen, which required some form of persistent storage. Enter GoInstant.

The decision to use GoInstant for Node Knockout was an easy one. Let’s be honest, they were giving away a Tessel as a prize! Luckily for us, they also provided an easy to use API for accessing a key-value store, which is exactly what we needed to implement our critical replay feature.

More specifically, we needed to store high scores, player names, and game state. Game state consisted of a terse JSON representation of the keyboard input over the course of the game, and a seed used to generate the cloud layout.

We needed the ability to have multiple clients reading from, writing to, and removing entries from this dataset concurrently, without leaving the data in an inconsistent state. GoInstant’s key-value store provided get/set primitives that let us easily add and read high scores, but since it also had a bunch of functionality we weren’t going to use, we opted to create a thin wrapper around it:

class NKO.Database GOINSTANT_URL = 'OUR_URL'
connect: (callback) -> if @connection callback.call(@) else goinstant.connect GOINSTANT_URL, (error, connection, lobby) => @lobby = lobby @connection = connection callback.call(@, error)
get: (key, callback) -> @lobby.key(key).get(callback)
set: (key, value, callback) -> @lobby.key(key).set(value, callback)
remove: (key, callback) -> @lobby.key(key).remove(callback)
NKO.database = new NKO.Database

Then to get our high scores, we simply connected and issued a get:

NKO.database.connect -> @get 'highScores', (error, highScores) -> unless error NKO.highScores = highScores

In order to concurrently remove items, each high score was given a UUID, and each client opportunistically pruned the dataset if the number of high scores we were storing creeped too high.

The client would issue a remove on the lowest scores and if the remove failed, we assume another client had already removed it and happily went on with our day. This remove happened when submitting new high scores, and the final code looked like this:

NKO.submitHighScore = (score, name, state, callback) -> id = UUID()
NKO.database.connect -> @set "highScores/#{id}", {name, score} @set "gameStates/#{id}", state
# clean up old high scores if NKO.highScores?.length > 100 squashId = (s, id) -> s.id = id s
remove = _(NKO.highScores).map(squashId).sortBy('score').rest(100).pluck('id')
if remove for id in remove @remove("highScores/#{id}", ->) if id @remove("gameStates/#{id}", ->) if id

Not very much code at all to get what we needed, which is especially important during a hack-a-thon. It’s also worth mentioning that while the API wasn’t free of bugs, the support via Twitter and IRC was timely and helpful. We received quick responses, even in the middle of the night.

You can tell these guys (and girls) are serious about making their product better. GoInstant also has a lot of cool real-time features, which we didn’t need to use this weekend, but which I will certainly look into going forward.

If you promise to be gentle, you can check out all our code from the weekend on GitHub.

Nick Kishfy

Share: