I was working on adding a “Computer” player to my Tic Tac Toe game and needed some way to dispatch functionality for making a player’s turn. I need to support both human-versus-human and human-versus-computer games.

There are a few different ways this can be done. One way would be to pass a map down with the associated function. So something like this might work, but I don’t like how this looks when we need to use the :next-move function.

(defn ->player [token next-move]
  {:token token 
   :next-move next-move})

(defn play-game [player-1 player-2]
  ;... Initialize player and game-board
  ((:next-move player) game-board (:token player))
  ;...
  )

(defn -main[]
  ;...
  (if player-vs-player?
    (play-game (->player \X request-player-move)
               (->player \O request-player-move))
    (play-game (->player \X request-player-move)
               (->player \O request-computer-move)))
  ;...
  )

This even has its drawbacks, however. For the computer’s turn, the next-move method needs to know about both player tokens, whereas in the human’s turn we need only know about that player’s token.

If we decided to go this route, then our “next-move” function declarations would look something like this:

(defn request-player-move [board token _] ...)
(defn request-computer-move [board token opponent] ...)

We’re not even using the second parameter, but we need it because of the way we’re calling it from at a higher-level function… that’s no good.

Dispatching via Protocols

Clojure has a useful tool called protocols, which you can think of almost as an interface or abstract class in OO languages.

So to solve the dispatching problem, we could create a Player protocol with a next-move method to get the player’s next move, and token to substitute for :token keyword we had in our maps earlier.

(defprotocol Player
  (next-move [this game-board])
  (token [this]))

Next we need to create types that implement these interfaces. You can think of types like you would a class in an OO language. We’ll call these types Human and Computer.

(deftype Human [token]
  Player
  (next-move [this board]
    ; Implementation ...
    )
  (token [_] token))

(deftype Computer [token opponent]
  Player
  (next-move [this board]
    ; Implementation ...
    )
  (token [_] token))

With these two types, we can now instantiate them in our main function and depend on the Player protocol rather than the types.

(defn play-game [player-1 player-2]
  ;... Initialize player and game-board
  (next-move player game-board)
  ;...
  )

(defn -main[]
  ;...
  (if player-vs-player?
    (play-game (->Human \X)
               (->Human \O))
    (play-game (->Human \X)
               (->Computer \O \X)))
  ;...
  )

Using protocols in our game, we are able to interface with subtypes using some predefined interface, and even define infinitely more Player types!

Now if we wanted a player that always loses, protocols will allow us to do that without worrying too much about how it will affect other parts of our code.