🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of defndaines

defndaines's solution

to Poker in the Clojure Track

Published at Oct 23 2018 · 0 comments
Instructions
Test suite
Solution

Pick the best hand(s) from a list of poker hands.

See wikipedia for an overview of poker hands.

Source

Inspired by the training course from Udacity. https://www.udacity.com/course/viewer#!/c-cs212/

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

poker_test.clj

(ns poker-test
  (:require [clojure.test :refer [deftest is]]
            [poker :refer [best-hands]]))

(defn f [xs ys] (= (sort (best-hands xs)) (sort ys)))

(deftest single-hand-always-wins
  (is (f ["4S 5S 7H 8D JC"] ["4S 5S 7H 8D JC"])))

(deftest highest-card-out-of-all-hands-wins
  (is (f ["4D 5S 6S 8D 3C"
          "2S 4C 7S 9H 10H"
          "3S 4S 5D 6H JH"]
         ["3S 4S 5D 6H JH"])))

(deftest a-tie-has-multiple-winners
  (is (f ["4D 5S 6S 8D 3C"
          "2S 4C 7S 9H 10H"
          "3S 4S 5D 6H JH"
          "3H 4H 5C 6C JD"]
         ["3S 4S 5D 6H JH"
          "3H 4H 5C 6C JD"])))

(deftest multiple-hands-with-the-same-high-cards-tie-compares-next-highest-ranked-down-to-last-card
  (is (f ["3S 5H 6S 8D 7H"
          "2S 5D 6D 8C 7S"]
         ["3S 5H 6S 8D 7H"])))

(deftest one-pair-beats-high-card
  (is (f ["4S 5H 6C 8D KH"
          "2S 4H 6S 4D JH"]
         ["2S 4H 6S 4D JH"])))

(deftest highest-pair-wins
  (is (f ["4S 2H 6S 2D JH"
          "2S 4H 6C 4D JD"]
         ["2S 4H 6C 4D JD"])))

(deftest two-pairs-beats-one-pair
  (is (f ["2S 8H 6S 8D JH"
          "4S 5H 4C 8C 5C"]
         ["4S 5H 4C 8C 5C"])))

(deftest both-hands-have-two-pairs-highest-ranked-pair-wins
  (is (f ["2S 8H 2D 8D 3H"
          "4S 5H 4C 8S 5D"]
         ["2S 8H 2D 8D 3H"])))

(deftest both-hands-have-two-pairs-with-the-same-highest-ranked-pair-tie-goes-to-low-pair
  (is (f ["2S QS 2C QD JH"
          "JD QH JS 8D QC"]
         ["JD QH JS 8D QC"])))

(deftest both-hands-have-two-identically-ranked-pairs-tie-goes-to-remaining-card-kicker
  (is (f ["JD QH JS 8D QC"
          "JS QS JC 2D QD"]
         ["JD QH JS 8D QC"])))

(deftest three-of-a-kind-beats-two-pair
  (is (f ["2S 8H 2H 8D JH"
          "4S 5H 4C 8S 4H"]
         ["4S 5H 4C 8S 4H"])))

(deftest both-hands-have-three-of-a-kind-tie-goes-to-highest-ranked-triplet
  (is (f ["2S 2H 2C 8D JH"
          "4S AH AS 8C AD"]
         ["4S AH AS 8C AD"])))

(deftest with-multiple-decks-two-players-can-have-same-three-of-a-kind-ties-go-to-highest-remaining-cards
  (is (f ["4S AH AS 7C AD"
          "4S AH AS 8C AD"]
         ["4S AH AS 8C AD"])))

(deftest a-straight-beats-three-of-a-kind
  (is (f ["4S 5H 4C 8D 4H"
          "3S 4D 2S 6D 5C"]
         ["3S 4D 2S 6D 5C"])))

(deftest aces-can-end-a-straight-10-J-Q-K-A
  (is (f ["4S 5H 4C 8D 4H"
          "10D JH QS KD AC"]
         ["10D JH QS KD AC"])))

(deftest aces-can-start-a-straight-A-2-3-4-5
  (is (f ["4S 5H 4C 8D 4H"
          "4D AH 3S 2D 5C"]
         ["4D AH 3S 2D 5C"])))

(deftest both-hands-with-a-straight-tie-goes-to-highest-ranked-card
  (is (f ["4S 6C 7S 8D 5H"
          "5S 7H 8S 9D 6H"]
         ["5S 7H 8S 9D 6H"])))

(deftest even-though-an-ace-is-usually-high-a-5-high-straight-is-the-lowest-scoring-straight
  (is (f ["2H 3C 4D 5D 6H"
          "4S AH 3S 2D 5H"]
         ["2H 3C 4D 5D 6H"])))

(deftest flush-beats-a-straight
  (is (f ["4C 6H 7D 8D 5H"
          "2S 4S 5S 6S 7S"]
         ["2S 4S 5S 6S 7S"])))

(deftest both-hands-have-a-flush-tie-goes-to-high-card-down-to-the-last-one-if-necessary
  (is (f ["4H 7H 8H 9H 6H"
          "2S 4S 5S 6S 7S"]
         ["4H 7H 8H 9H 6H"])))

(deftest full-house-beats-a-flush
  (is (f ["3H 6H 7H 8H 5H"
          "4S 5H 4C 5D 4H"]
         ["4S 5H 4C 5D 4H"])))

(deftest both-hands-have-a-full-house-tie-goes-to-highest-ranked-triplet
  (is (f ["4H 4S 4D 9S 9D"
          "5H 5S 5D 8S 8D"]
         ["5H 5S 5D 8S 8D"])))

(deftest with-multiple-decks-both-hands-have-a-full-house-with-the-same-triplet-tie-goes-to-the-pair
  (is (f ["5H 5S 5D 9S 9D"
          "5H 5S 5D 8S 8D"]
         ["5H 5S 5D 9S 9D"])))

(deftest four-of-a-kind-beats-a-full-house
  (is (f ["4S 5H 4D 5D 4H"
          "3S 3H 2S 3D 3C"]
         ["3S 3H 2S 3D 3C"])))

(deftest both-hands-have-four-of-a-kind-tie-goes-to-high-quad
  (is (f ["2S 2H 2C 8D 2D"
          "4S 5H 5S 5D 5C"]
         ["4S 5H 5S 5D 5C"])))

(deftest with-multiple-decks-both-hands-with-identical-four-of-a-kind-tie-determined-by-kicker
  (is (f ["3S 3H 2S 3D 3C"
          "3S 3H 4S 3D 3C"]
         ["3S 3H 4S 3D 3C"])))

(deftest straight-flush-beats-four-of-a-kind
  (is (f ["4S 5H 5S 5D 5C"
          "7S 8S 9S 6S 10S"]
         ["7S 8S 9S 6S 10S"])))

(deftest both-hands-have-straight-flush-tie-goes-to-highest-ranked-card
  (is (f ["4H 6H 7H 8H 5H"
          "5S 7S 8S 9S 6S"]
         ["5S 7S 8S 9S 6S"])))
(ns poker)

(def ranks
  "Rank mapping of face value to a character that can be used to sort
  alphabetically."
  {"A" \0
   "K" \1
   "Q" \2
   "J" \3
   "10" \4
   "9" \5
   "8" \6
   "7" \7
   "6" \8
   "5" \9
   "4" \:
   "3" \;
   "2" \<})

(def low-ace-rank \=)

(defn ace-low
  "Change the rank of any Ace cards to treat it as the low card."
  [cards]
  (map
    (fn [card]
      (if (= "A" (:face card))
        (assoc card :rank low-ace-rank)
        card))
    cards))

(defn split-cards
  "Parse and break apart cards into sortable values."
  [hand]
  (map
    (fn [card]
      (let [[[_ f s]] (re-seq #"(\w+)([SHCD])" card)]
        {:face f
         :rank (ranks f)
         :suit s
         :hand hand}))
    (clojure.string/split hand #" ")))

(defn report
  "Create a tuple with a sortable rank value followed by the original hand."
  [cards hand-rank]
  [(clojure.string/join (conj (map :rank cards) hand-rank))
   (:hand (first cards))])

(defn sort-by-freq
  "Sort grouped cards by their frequency then rank. Most frequent cards are at
  the end, and when sets of cards have the same frequency, sort in ascending
  rank order."
  [grouped-cards]
  (sort-by (comp count second) grouped-cards))

(defn straight-ace-high
  "If the hand is a straight with Ace high, return the sorted cards."
  [cards]
  (let [sorted (sort-by :rank cards)]
    (when (apply = (map +
                        (map #(int (:rank %)) sorted)
                        (range 4 -1 -1)))
      sorted)))

(defn straight-ace-low
  "If the hand is a straight with Ace low, return the sorted cards."
  [cards]
  (let [sorted (sort-by :rank (ace-low cards))]
    (when (apply = (map +
                        (map #(int (:rank %)) sorted)
                        (range 4 -1 -1)))
      sorted)))

(defn straight-flush? [cards]
  (when (= 1 (count (set (map :suit cards))))
    (cond
      (straight-ace-high cards) (report (sort-by :rank cards) 0)
      (straight-ace-low cards) (report (sort-by :rank (ace-low cards)) 0))))

(defn four-of-a-kind? [cards]
  (let [g (group-by :face cards)]
    (when (= 2 (count g))
      (let [[[_ [high-card]] [_ quad]] (sort-by-freq g)]
        (when (= 4 (count quad))
          (report (conj quad high-card) 1))))))

(defn full-house? [cards]
  (let [g (group-by :face cards)]
    (when (= 2 (count g))
      (let [[[_ pair] [_ tripel]] (sort-by-freq g)]
        (when (and (= 2 (count pair))
                   (= 3 (count tripel)))
          (report (concat tripel pair) 2))))))

(defn flush? [cards]
  (when (= 1 (count (set (map :suit cards))))
    (report (sort-by :rank cards) 3)))

(defn straight? [cards]
  (cond
    (straight-ace-high cards) (report (sort-by :rank cards) 4)
    (straight-ace-low cards) (report (sort-by :rank (ace-low cards)) 4)))

(defn three-of-a-kind? [cards]
  (let [g (group-by :face cards)]
    (when (= 3 (count g))
      (let [[[_ [a]] [_ [b]] [_ tripel]] (sort-by-freq g)]
        (when (= 3 (count tripel))
          (report (concat tripel (sort-by :rank [a b])) 5))))))

(defn two-pairs? [cards]
  (let [g (group-by :face cards)]
    (when (= 3 (count g))
      (let [[[_ high-card] [_ first-pair] [_ second-pair]]
            (sort-by-freq g)]
        (when (and (= 2 (count first-pair))
                   (= 2 (count second-pair)))
          (report (concat second-pair first-pair high-card) 6))))))

(defn one-pair? [cards]
  (let [g (group-by :face cards)]
    (when (= 4 (count g))
      (let [[[_ [a]] [_ [b]] [_ [c]] [_ pair]]
            (sort-by-freq g)]
        (report (conj pair c b a) 7)))))

(defn high-card [cards]
  (report (sort-by :rank cards) 8))


(defn rank-hand [hand]
  (let [cards (split-cards hand)]
    (some identity
          (map
            #(% cards)
            [straight-flush?
             four-of-a-kind?
             full-house?
             flush?
             straight?
             three-of-a-kind?
             two-pairs?
             one-pair?
             high-card]))))


(defn best-hands [hands]
  (let [ranked (sort-by first (map rank-hand hands))
        best-rank (ffirst ranked)]
    (keep
      #(when (= best-rank (first %)) (second %))
      ranked)))

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?