Playing (cards) with Clojure

I’m learning Clojure, which is a modern Lisp, which is hosted on the JVM. I’m so far really liking the focus on integrating the benefits of functional programming into a modern environment. It is pragmatic and reasonable. It feels like programming for grown-ups, in the same way that Node.js feels like programming for people with more energy than sense.

While riding on the train with my kids, I told them, “Hey, I’m learning this new language, let’s use it to make a card game since we don’t have any cards with us.” Clojure, as a Lisp, is meant to be usable interactively from a REPL and since I was on my phone in a train, I used an in-browser version of Clojure called ClojureScript. The one I used is at http://trycojure.org.

So, to play with cards, we need a deck. I used the for function to iterate over a pair of variables for the cards and the suits:

(def deck (for [rank (take 13 (drop 1 (range))) 
                  suit [:♥️ :♦️ :♣️ :♠️]]
              {:rank rank :suit suit}))

Range starts at 0, but cards run from 1 to 13. So we need to drop 1 off the front of range before taking 13 more items from the infinite list it provides.

Now we can play with our deck a bit:

; take the first 3 cards off and look at them
(take 3 deck)
> ({:rank 1, :suit :♥️} {:rank 1, :suit :♦️} {:rank 1, :suit :♣️})

; take the next 3 cards off and look at them
(take 3 deck)
> ({:rank 1, :suit :♥️} {:rank 1, :suit :♦️} {:rank 1, :suit :♣️})

So we see two things there. First, we see how simple and beautiful it is to make an “object” in Clojure: just use a map to associate various types of data (including Unicode strings) together. Second we see the “consequences” of Clojure’s immutable data: taking the “first 3 cards” and the “next 3 cards” doesn’t do what you might expect: we get the same 3 cards, because both calls to take are making new 3 element immutable lists based off of the same original immutable list.

So then how do we deal out two hands of 3 cards? What we need to do is shuffle the cards, and then take our hands from this new list. We can take 3 for one hand and 3 for the other hand:

(let [shuffled (shuffle deck)
        you (take 3 shuffled)
        me (take 3 (drop 3 shuffled))]
    {:you you :me me})
> {:you ({:rank 1, :suit :♦️}
         {:rank 12, :suit :♠️}
         {:rank 9, :suit :♣️}),
   :me ({:rank 8, :suit :♠️}
        {:rank 6, :suit :♣️}
        {:rank 6, :suit :♥️})}

The key to getting the benefits of pure functions and immutable data structures is to adapt to them, instead of fighting them. So here, we need to put a name on the copy of the deck that’s been shuffled, so that we can take cards from the same shuffle for the two players.

I wondered how shuffle works, and I got an interesting surprise when I looked at “view source” for it in VS Code. (I use VS Code and Calva to program Clojure. The first time I learned Lisp, in university, I was working within a not-very-good editor inside of a not-very-good Scheme implementation for MacOS System 7. It crashed a lot. The next time I re-learned Lisp was in 2006, when I did Abdulaziz Ghuloum’s step by step Scheme compiler tutorial. Then, I worked in Emacs, which I like. But I hate configuring Emacs, and so I was delighted when VS Code came on the scene and let me forget what microscopic amount of e-lisp I ever managed to learn.)

Here’s how Clojure implements shuffle:

(defn shuffle
  "Return a random permutation of coll"
  {:added "1.2"
   :static true}
  [^java.util.Collection coll]
  (let [al (java.util.ArrayList. coll)]
    (java.util.Collections/shuffle al)
    (clojure.lang.RT/vector (.toArray al))))

That’s interesting because it demonstrates the Java interoperation part of Clojure. In general, the Clojure standard library has a lot of “batteries included”, and when it makes sense, they depend on existing “batteries” included in Java itself. In this case, they are using java.util.Collections’ shuffle​(List<?> list), which shuffles in-place. That’s why they need to make a copy of the collection as an ArrayList and then construct a new Clojure vector from the array before returning it. Typical Java code does not use immutable data, and so when you do interoperation with it, getting the output data back to normal Clojure data structures as soon as possible is important to regain the benefits of immutable data.

There is another shuffle implementation in Java that allows you to pass in your own Random object. Using that, you’d be able to get deterministic shuffles, which is something we used when investigating how to put games on decentralized ledgers when I worked at DEDIS. As a way to play with Java interoperation in Clojure, perhaps I will try to make a deterministic shuffle.

To have some kind of game at all, I wanted to add up the cards. So I used reduce to do that. I had a lot of trouble understanding reduce the first few times I read about it, until I read what Clojure for the Brave and True had to say about it. Now I understand that reduce often means “go do this thing for each item in the list, and build up one result from visiting all those things”. That helped me see how to use it here to add cards:

(defn add-cards [coll]
  (reduce #(+ % (:rank %2)) 0 coll))

; a micro-unit test: the first 3 cards, all with rank 1, should add to 3
(= (add-cards (take 3 deck)) 3)
> true

Here we also see the supremely pragmatic technique in Clojure of using a keyword as a function. The underlying machinery that implements keywords in Clojure implements the IFn interface, which means you can call into it from Clojure. When a keyword is called, it looks at it’s first argument, and if it is a map, it will look itself up in the map. The smallest possible demonstration of this is: (= 3 (:rank {:rank 3})) -> true

And so now, we just need to put it together into a boring little game, which though it bored my children to tears, at least helped me pass a little time on a long train trip and practice my new Clojure skills:

(defn game []
  (let [shuffled-cards (shuffle deck)
        you (take 3 shuffled-cards)
        me (take 3 (drop 3 shuffled-cards))
        you-score (add-cards you)
        me-score (add-cards me)] 
    {:you you :me me, :winner (if (> you-score me-score) :you :me)}))

> (game)
{:you ({:rank 5, :suit :♦️} {:rank 7, :suit :♣️} {:rank 3, :suit :♠️}),
 :me ({:rank 2, :suit :♠️} {:rank 7, :suit :♠️} {:rank 3, :suit :♦️}),
 :winner :you}
> (game)
{:you ({:rank 7, :suit :♦️} {:rank 12, :suit :♥️} {:rank 8, :suit :♥️}),
 :me ({:rank 12, :suit :♦️} {:rank 11, :suit :♦️} {:rank 5, :suit :♣️}),
 :winner :me}
> (game)
{:you ({:rank 4, :suit :♠️} {:rank 5, :suit :♦️} {:rank 5, :suit :♥️}),
 :me ({:rank 13, :suit :♠️} {:rank 5, :suit :♠️} {:rank 11, :suit :♥️}),
 :winner :me}

I win!