## DEV Community

Chakrit Likitkhajorn

Posted on

# Design a program in Functional #2 Poker Kata

In this article, I will demonstrate this again using Poker kata. The question is very simple, given two poker hand, determine who is winning.

## Start modeling

So at the end, what we need is a function

``````compare(hand1, hand2) -> Win, Lose, Draw
``````

And we can model a data shape of hand, card as followed:

``````object Suit extends Enumeration {
type Suit = Value

val Heart = Value("Heart")
val Club = Value("Club")
val Diamond = Value("Diamond")
}
case class Card(value: Int, suit: Suit.Suit)
type Hand = (Card, Card, Card, Card, Card)
``````

Basically, we just told the compiler that a card is data consists of value as an integer, and a suit which can be either Heart, Spade, Club or Diamond. Then, a hand is a set of 5 cards.

I will represent Jack, Queen, King and Ace with 11, 12, 13 and 14 respectively.

## First design

At the end, we want to implement

``````def comparePokerHand(hand1: Hand, hand2: Hand): CompareResult.CompareResult
``````

Ok. I will be the first to admit that implement `comparePokerHand` from the get go will be hard and complicated.

So I need to break it down.

Here comes the magic question: What is a data A in such that?

1. It is simpler to calculate a hand comparison result from data A.
2. It is simpler to calculate data A from each hand.

Creative thinking time.....
...
...
...

Ok, if I have the rank of each hand (Straight, Flush, Two Pairs, etc.) and highs of each hand, then I can simply compare those. I will compare hand power first, and if hand power is the same, then I will compare each highs until the end.

Here is the first breakdown pseudo code.

``````case class HandPower(rank: HandRank, highs: List[Int])

def comparePokerHand(hand1: Hand, hand2: Hand): CompareResult = {
val handPower1 = handPower(hand1)
val handPower2 = handPower(hand2)
compareHandPower(handPower1, handPower2)
}
``````

Please note that in order to make above statement to be true, the highs must be ordered from the most significant card value to least significant card value.

For example:

``````Flush(1 3 10 5 4) -> Highs(10 5 4 3 1) // Order from highest value to lowest value
TwoPairs(3 3 5 5 10) -> Highs(5 3 10) // The pairs come first, sorted by higher value, then the non-pair
FullHouse(5 5 5 10 10) -> Highs(5 10) // Three of kind first, then pair
``````

If we have this, then we can easily compare highs by looking at each element one-by-one.

For example:

``````FullHouse(5 5 5 10 10) lose FullHouse(9 9 9 2 2)
Highs (5 10) lose to Highs(9 2)
5 < 9

Two Pairs(10 10 14 14 4) win TwoPairs(9 9 14 14 5)
Highs(14 10 4) win Highs(14 9 5)
14 == 14
10 > 9
``````

This is how having a data `highs` with above properties make it easier to determine a winner.

Having this all said, next I need to implement two functions: `HandPower` and `compareHandPower`.

I will go through the easier one first: `compareHandPower`. This function will be implemented according to what I thought

I will compare hand power first, and if hand power is the same, then I will compare each highs until the end.

``````def compareRank(
handRank1: HandRank.HandRank,
handRank2: HandRank.HandRank
): CompareResult.CompareResult = {
if (handRank1.id < handRank2.id) return CompareResult.Win
if (handRank1.id > handRank2.id) return CompareResult.Lost
CompareResult.Draw
}

def compareHighs(
high1: List[Int],
high2: List[Int]
): CompareResult.CompareResult = {
high1
.zip(high2)
.foldLeft(CompareResult.Draw)((result, pair) => {
if (result != CompareResult.Draw) {
return result
} else if (pair._1 > pair._2) {
return CompareResult.Win
} else if (pair._1 < pair._2) {
return CompareResult.Lost
} else {
return CompareResult.Draw
}
})
}

def compareHandPower(
handPower1: HandPower,
handPower2: HandPower
): CompareResult.CompareResult = {
val rankComparison = compareRank(handPower1.rank, handPower2.rank)
if (rankComparison == CompareResult.Draw) {
return compareHighs(
handPower1.highs,
handPower2.highs
)
}
return rankComparison
}
``````

Easy peasy!! (Hope you find this simple as well). First function done 😄

Then next, the `handPower` function.

The hand power function must consist of two ability

• Ability to generate rank from hand
• Ability to generate highs from hand

I have to admit that at first, I cannot figure out how to break it down.

So I start with a very simple and naive question: How can I determine if this hand is a four card hand?

The answer is simple: I need to check if any card value occurs four times within a hand. That means I need to determine the frequencies of each card value.

So I start implemented a `freqMap` like this:

``````val freqMap =
hand.foldLeft(Map[Int, Int]())((acc, card: Card) => {
acc.get(card.value) match {
case None              => acc + (card.value -> 1)
case Some(currentFreq) => acc + (card.value -> (currentFreq + 1))
}
})
``````

Basically, `freqMap` is a map between card value and card frequency.

With this, I can write the logic for four cards to be

``````if (freqMap.exists(keyValuePair => {
val cardFrequency = keyValuePair._2
cardFrequency == 4
})) {
return HandPower(HandRank.FourCard, highs)
}
``````

Ok. Now I can check four cards. Let's check for the full house.

``````if (
freqMap.exists(keyValuePair => {
val cardFrequency = keyValuePair._2
cardFrequency == 3
}) && freqMap.exists(keyValuePair => {
val cardFrequency = keyValuePair._2
cardFrequency == 2
})
) {
return HandPower(HandRank.Fullhouse, highs)
}
``````

Now I realize that things start to get messy and duplicates.

Back to the core question: Is there any data A that will make our life easier?

Answer: Yes. If we have data A that is a list of frequencies of each card value sorted, we can easily determine many card ranks.

How? I think it is easier to show you by this code:

``````cardFrequenciesPattern match {
case 1 :: 4 :: Nil      => return HandPower(HandRank.FourCard, highs)
case 2 :: 3 :: Nil      => return HandPower(HandRank.Fullhouse, highs)
case 1 :: 1 :: 3 :: Nil => return HandPower(HandRank.ThreeOfKind, highs)
case 1 :: 2 :: 2 :: Nil => return HandPower(HandRank.TwoPairs, highs)
case 1 :: 1 :: 1 :: 2 :: Nil =>
return HandPower(HandRank.OnePair, highs)
case _ => {
// Something
}
}
``````

You can see that if a `cardFrequenciesPattern` exists, and it is a frequencies of each card value sorted, we can easily match it to a corresponding pattern.

So the data A for this case is `cardFrequenciesPatter`.

I wrote a transformation from current `freqMap` to easier `cardFrequenciesPattern` as followed:

``````val cardFrequenciesPattern = freqMap.values.toList.sorted
``````

What's left for me is to write some if-else for straight, flush and nothing in the last case.

Since we were able to determine a rank, let us determine highs.

Definition: highs is a list of card values in hand, ordered from the most significant card value to least significant card value.

I notice that the most significant card values in any hand are also the most frequent card in hand. For example: In full house hand, three kinds of cards are considered to be more significant than a pair of cards.

So the logic could be order card values based on frequency first, then order by the values itself.

And since we already have a map of card frequency and card value, then here it is:

``````val highs = freqMap.toSeq
.sortWith((pair1, pair2) => {
val cardVal1 = pair1._1
val cardVal2 = pair2._1
val cardFreq1 = pair1._2
val cardFreq2 = pair2._2
if (cardFreq1 == cardFreq2) {
cardVal1 > cardVal2
} else {
cardFreq1 > cardFreq2
}
})
.map(_._1)
.toList
``````

Surprisingly, the intermediate `freqMap` data make it very easy to determine both `highs` and `rank`.

Now we got everything: We have a function to create a hand power from a hand. We have a function to compare two hand power. That's mean we solved Poker Kata.

Done deal! 🎉🎉

Full implementation in Scala: https://github.com/chrisza4/scala-poker-kata

Full implementation in Clojure: https://github.com/chrisza4/clojure-poker-kata

## Recap and Lesson

In this article, I demonstrate the thought process and program design that repeatedly ask a simple question: Is there data A?

And by asking ourselves this question, we found that:

• Intermediate data `HandPower` make it way easier to compare poker hand easier.
• Intermediate data `freqMap` make it way easier to both create a rank and highs.
• Intermediate data `cardFrequenciesPattern` make it way easier to determine a card rank

This is the functional thinking. That is how we breakdown a problem in a functional paradigm. This is what it means to "write more functions".

Again, as I wrote in the previous article, this thinking and thought process line is useful even if you write a program in an imperative or object-oriented paradigm.