Sente Style Multiplayer Snake in ClojureScript

Watch the Screencast
Read the Source Code
Play the game Standalone

Coder X here,


Today we will build a multiplayer snake game. Players connect to a central server and eat green food dots to grow. We will use websockets to communicate between the browser and server.
One concern when using websockets is browser support. Sente is a library which addresses this concern by falling back to ajax when websockets are not available.




We will use Sente to collect player direction changes, and push out the new state of the world every time it updates. Let’s get started with a new ClojureScript project.

First we create the game client. We set up three namespaces:
  • Main - the entry point that initializes our game
  • Model - to store and manipulate the data for our game
  • View - for components to be rendered on the page


Our game board will be a 20 by 20 matrix. We store our board and players in app-state. Players will have a current position, direction, length, and tail path. We need a ticker that updates the game world at regular intervals. Each update will be calculated by the step function.

(defn new-board []
  (vec (repeat width (vec (repeat height nil)))))

(defonce world
  (ref {:board (new-board)
        :players {}}))

Let’s see how we might draw the game board. For each position in the matrix, we may draw a snake body segment. We start a ticker that updates player positions, and some code to trim the tails of snakes as they move. Snakes are slithering.

Let’s adjust the look of the segments to taste. We need to handle keyboard input, so let’s add a listener that sets the next direction the snake should travel in. Great, we can steer the snake around now. Let’s add some eyes so it is obvious where the head is. We put two circle elements inside a group element that is translated to the player location.

(defn segment [uid i j me?]
  [:rect
   {:x (+ i 0.55)
    :y (+ j 0.55)
    :fill (subs uid 0 7)
    :stroke-width 0.3
    :stroke (subs uid 7 14)
    :rx (if me? 0.4 0.2)
    :width 0.9
    :height 0.9}])

(defn food [i j]
  [:circle
   {:cx (inc i)
    :cy (inc j)
    :r 0.45
    :fill "lightgreen"
    :stroke-width 0.2
    :stroke "green"}])

(defn pixel [uid i j my-uid]
  (if (= uid "food")
    [food i j]
    [segment uid i j (= my-uid uid)]))

(defn eye [dx dy]
  [:circle
   {:cx (/ dx 2)
    :cy (/ dy 2)
    :r 0.2
    :stroke "black"
    :stroke-width 0.05
    :fill "red"}])

We are now ready to work on the server side of our game. Let’s copy the example code from the Sente website to set up our websockets. On the server side we have compojure routes to accept the socket. We’ll host these routes in a HTTP-kit server because HTTP-kit supports websockets.

Ring reload middleware is essential for development. It will watch the filesystem for changes in our code, and load them when we call the server. When we make changes, we don’t need to restart or send to our REPL integration. We just save our changes, and see the effect immediately.

The server is receiving events. The events are identified with a unique keyword. We dispatch handling of events with a multimethod. Our first custom handler will be for the :snakelake/dir event.
We’ll start by just printing the event we receive.

(defmethod event :snakelake/dir [{:as ev-msg :keys [event uid ?data]}]
  (let [[dx dy] ?data]
    (model/dir uid dx dy)))

Now we will move the model code we built up in our client code over to the server. Isn’t it convenient that we are using Clojure in both places? Instead of an atom, the server should use a ref to model world state. Refs provide transactional guarantees, and we want to handle concurrent user access.


When players connect, we’ll assign them a random colors as their identifier. The ticker will be a thread instead of an interval callback. We’ll broadcast the entire world every tick. On the client side, our model now gets replaced with whatever we hear from the server. Our client sends directions to the server, which updates the player state in the server model. Every tick, the world is updated and broadcast to all connected clients. When we run two browsers, we can see them both updating together.



(defn dir [dx dy]
  (chsk-send! [:snakelake/dir [dx dy]]))


At this point we have a multiplayer game. The server and client message handling code is very similar. Communication is expressed as event handling.


We need a host that will run our server process, not just serve static files. Heroku has a free hosting option, easy deployment steps, and excellent documentation. We define how the server should run our code in a Procfile. We’ll use java to execute an uberjar. An uberjar contains all our code, compiled Javascript, HTML resources, and dependencies. With this uberjar, the host can run the game loop process, serve files, and respond to requests.


   :uberjar {:hooks [leiningen.cljsbuild]
             :aot :all
             :cljsbuild {:builds
                         [{:id "min"
                           :source-paths ["src" "prod"]
                           :compiler {:main snakelake.main
                                      :output-to "resources/public/js/compiled/snakelake.js"
                                      :optimizations :advanced
                                      :pretty-print false}}]}}}


As this is a server process, we have the option to enable ahead of time compilation. Ahead of time compiled code starts faster, but it brings some weirdness. We need to pay careful attention to the Sente Lifecycle. Top level forms are executed during ahead of time compilation. It doesn’t make any sense to establish a socket server while compiling. So we need to reorganize our code a little bit to have a start function. Alternatively we could have chosen to avoid AOT completely..


We have our deployment unit, so let’s try running it locally. Making adjustments locally is faster than a full round trip deploy. Ok, things are looking good, so let’s deploy to the hosting provider.
Push the repository to Heroku, and our game is now live.


Setting up bidirectional communication is easy! The communication model is simple and concise.
Clojure lends itself to separation of concerns:
  • Communication is event handling oriented.
  • The model is data and transformations.
  • The interface is a function of the data.
Most importantly, building things is fast and fun.


I hope you enjoy playing Snake Lake, and that you will build something even better.


Until next time,
Keep coding.


Do not settle for a REPL transcript

Transcript for "Do not settle for a REPL" screencast:

CoderX here.

Today we will be using test-refresh to find influential Twitter friends. The goal is to illustrate why code reload is better than a REPL. Test-refresh watches the file-system, reloads code, and runs tests. Jake McCrary wrote test-refresh while sporting a smart bow-tie. Programming is serious business.




A REPL is a read eval print loop. Forms are read interactively, evaluated, and the result of evaluation is printed to the screen. But code reloading is better than a REPL.
So do not settle for a REPL.
 vs 

There are three mature code reload workflows in Clojure:
  • Figwheel. We showed off how cool Figwheel is in previous episodes.
  • Ring reload middleware. An absolute must for server side development.
  • Test-refresh. The focus of this episode. Beats REPLs hands down.

They all watch files and reload code. The trigger for code evaluation is when we save a file,
which is usually the right punctuation point in our workflow. Saving files works with any editor with no special plugins or keystrokes. The model is easy to understand and think about.


Before we get coding, I’ll make a quick disclaimer. This is not a testing rant.
This is about using code reload for an interactive workflow. So don’t be surprised when I don’t write tests… I want to compare apples to apples, experimenting from the REPL with experimenting from test-refresh.


To find influential Twitter friends I need to Connect to Twitter. Fetch a list of my friends. For each friend, fetch their friends. And then Pagerank the network.




Let’s start a new project called twitternet. Add twitter-api to our project dependencies
Navigate to apps.twitter.com and click “create new app”. I deleted this twitter app after gathering the data I needed, so these credentials are no longer live. You will need to create your own credentials. Add clj-http as a dependency.


Let’s make some calls by copying the examples. A few print statements allows us to examine the shape of the data. At this point we discover that rate limiting is quite severe, only 1 request per minute is allowed. Let’s be sure to save the output to files so we can use them later.



(ns twitternet.core
  (:require
    [twitter.oauth :as oauth]
    [twitter.api.restful :as rest]))

(def my-creds
  (oauth/make-oauth-creds
    "pfraQVsp9gYM1hxENMVfSfMBQ"
    "uDFD9sEjTQjholVJkItJXyo11Suvjsx4Gk5KKiBMmNQzUDUfo9"))

(defn fetch-friends [id]
  (Thread/sleep 60000)
  (println "Fetching friends" id)
  (doto (:ids (:body (rest/friends-ids
                       :oauth-creds my-creds
                       :params {:id id
                                :count 200
                                :skip_status true
                                :include_user_entities false})))
    (->> (spit (str id ".txt")))))

(defn fetch-user [screen-name]
  (println "Fetching user" screen-name)
  (:body (rest/users-show :oauth-creds my-creds
                          :params {:screen_name screen-name})))

(defn get-network []
  (let [my-id (:id (fetch-user "timothypratley"))
        my-friends (fetch-friends my-id)]
    (into {my-id my-friends}
          (for [friend my-friends]
            [friend (fetch-friends friend)]))))

(spit "network.txt" (pr-str (get-network)))



Ok let’s let this baby roll! I follow over 100 people, so this is going to take nearly 2 hours… All done! We have a file representing my network.


My immediate network consists of people I follow, those people follow people outside my immediate network, but also follow some people that I follow. This person has 3 people following them. This person has 2 people following them. But only I am following this person.





We can rank people based on the number of links. Pagerank uses current ranks to generate new ranks iteratively. Each link is given a weight based upon the current score of the source node, with which to calculate a new score for the target node. This is repeated until the scores settle. The interesting thing about Pagerank is that the quality of inbound links matters. Here yellow is recognized as important because blue links to it.



We will use LeaderboardX to do a Pagerank on our network. First we need to reshape the data into the expected input format. We want to generate lines of lists of people and who they follow. Great, now we can load our file. Oh no, our graph is way too large. There are 17 thousand nodes, so rendering them all is not possible. If we filter out nodes not in my immediate network there are only 15 hundred.



(ns twitternet.munge
  (:require
    [clojure.edn :as edn]
    [clojure.string :as string]))

(defn transform [network]
  (for [[person outs] network]
    (cons person (filter network outs))))

(defn reshape []
  (let [network (edn/read-string
                  (slurp "network.txt"))]
    (spit
      "lxnet.txt"
      (string/join
        \newline
        (cons
          "Person,Endorses"
          (map #(string/join "," %)
               (transform network)))))))


Success! My network looks like a hairball. There are many connections and nodes.


Notice that the top ranked member has fewer links than second place.



The runner up for highest Pagerank in my immediate network is
Bruce Hauman, the author of Figwheel and Devcards.
And the winner is, Shaun LeBron, the author of Parinfer.


Let’s reflect for a moment and compare test-refresh with a REPL.
  • There is only the code file to edit
  • There are no special keystrokes to evaluate forms or send them to the REPL
  • We get instant notification when code fails to compile or execute

I love tmux, emacs, and vim... but when it comes to REPL integration, things get pretty complicated.
It feels productive to have key combos to execute code, run tests, switch buffers, splice in results and all sorts of great stuff.

However I end up spending a lot of time switching window focus, sending code to the REPL, finding tests to run, forgetting to eval my function or file, and generally being busy interacting with the REPL. I make many mistakes, and blame myself for not being able to keep it all straight in my head.

In contrast, when I use test-refresh with any editor, my workflow is very simple. There is my code and there is the log of what happens when it reloads. From this simplicity flows productivity because I can focus on my code. My primary brain function is thinking about the program, not managing my REPL.




Cursive, by Colin Fleming is well suited to this workflow because Cursive’s error detection, documentation and navigation features do not require a REPL.



If you are new to Clojure, I strongly encourage you to stick with your most comfortable editor for as long as possible. Learning a language is hard enough without learning a new editor at the same time. Test-refresh provides fast feedback without the need for any integration.


To set test-refresh up, add it to your lein profile.clj
I highly recommend configuring the “changes only” and “quiet” options.
These options greatly reduce the amount of time and noise per refresh.




Ultra provides nicely formatted diffs when tests fail. To see it in action let’s write a basic test.




(ns twitternet.munge-test
  (:require
    [clojure.test :refer :all]
    [twitternet.munge :as munge]))

(deftest transform-test
  (is (= [[1 2 3]
          [2]
          [3 1 2]]
         (munge/transform {1 [2 3 4]
                           2 [5 6 7]
                           3 [1 2 8]}))))




Ancient will upgrade project dependencies, if all the tests pass. Kibit detects non-idiomatic code. Eastwood detects bad code. And bikeshed detects bad formatting.


Sometimes I don’t want to create a project to experiment with Clojure. Try CLJ is pretty handy for this because there is no startup time. LightTable has an instarepl which shows results inside the file you are currently editing. To see it in action, let’s try answering a StackOverflow question.
Let’s try this code out and see what we get. Hmmm the problem seems to be with the type conversions here. Yeah, they either need to make a true random bigint, or if a restricted domain of random numbers is acceptable do some extra casting.


I occasionally use LightTable like this for throw away code... but for most of my work, I want to keep the code around.


Using test-refresh as a REPL replacement has made my coding workflow more effective. And it has encouraged me to add tests at times I would otherwise felt that was a chore. Next time you are about to lein repl, lein test-refresh instead.

Do not settle for a REPL.


Until next time, keep coding.