Clojurescript HTML5 Tetris
On: 25th May 2017
Backstory
For a while now I wanted to build my own version of Tetris as a fun side project. I had always planned to do this as an HTML5 Canvas game. The reason for this is that I have had experience making a game this way in the past and felt that it is an easy way to make a game. It's also great that it runs in a web browser meaning I can host it on a website.
When I first set out to do this it was around two years ago. I decided I would write it in plain Javascript. This is fine but I eventually ended up giving up for some reason. The problem I find with writing a large application like this in javascript is separating the code into manageable chunks. This is where Clojurescript comes in. Around a year ago I got a project which was fully based on Clojure. The backend was Clojure and the front end was fully written and templated in Clojurescript. At this point, I realized the Clojurescript is a really nice language to write large browser applications in. I then had the idea of trying Tetris again in Clojurescript. Again I failed.
Earlier this year I decided to try once again, this is where things happened. This time I had a year of Clojurescript experience so I knew my way around the code well. This is where I successfully managed to create the game. This was truly one of the most fun projects I have done. Working on it in Clojurescript really made it easier over plain javascript.
The Making
The way Clojurescript works is it a functional programming language that allows you to separate the code but namespaces. This is an instant benefit over Javascript. The Clojurescript then can build out all of these namespaces into javascript then load them all into your webbrowser though a single javascript file. This is great. Creates an easy way to organize and separate the code.
One of the key challenges with working with Clojurescript is the issue of state. A game needs to store allot of state. The way Clojure and Clojurescript are designed is to be a stateless immutable language. The is a mechanism for storing state which is called an atom. At the time when I started building the game, I opted to store bits of state in atoms. Over time the amount of state that built up grew substantially and now this is quite a large amount of atoms in the application. This is something that I am certainly not proud of as it majorly goes against the philosophy of the language.
One way I have thought of to rectify this issue is rather than storing all that is variables, I could pass my main render function an object containing all of the state. As that function runs though it could make changes to this object. Then at the end, it returns a new state object that can then be passed back in on the next render. In hind site, I feel like this is how I should of build the game in the first place. Retrofitting this now would be a challenge.
I have found that I have managed to build this whole game without including any major dependencies. There are no libraries on top of Clojurescript to build this. I just used the javascript interop to create a small abstraction namespace to give me access to the HTML5 Canvas API. I also made a small namespace to store the high scores in local storage. Pretty much all else is written in pure Clojurescript
The result
I'm very happy with the end result. It performs well and works in all modern browsers. It also scales well to mobile. It's fun and feels reasonably comparable to some of the original Tetris games.
What's next?
I would like to build an online scoreboard for the game. At this point, I have not decided the best way to do this. I would like to make the project completely Clojure based but I have a niggle. I'm hosting this on a web server that fully setup for running PHP. It seems a little crazy to build the backend in Clojure just for creating a little database API when I have PHP already running and PHP can do this well. If I do it in Clojure I have to run a JVM on my server running the Clojure server instance, then I have to configure my webserver to proxy the API request to the Clojure application. This isn't a big deal and is probably the route I will go down but it just feels a little overkill.
Links
Please have a go at the game:
If you are interested in the code, It is open source. Check it out on Github.