Design a program in Functional #2 Poker Kata
Tags: functional, scala, architecture
Published: 2020-12-18 02:47:11

From [last article](https://dev.to/chrisza4/design-a-program-in-functional-way-1-core-concept-and-bowling-example-3b8k), I demonstrated how to think about program design in functional way.

In this article, I will demonstrate this again using [Poker kata](https://codingdojo.org/kata/PokerHands/). 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:

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

  val Heart = Value("Heart")
  val Spade = Value("Spade")
  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

```Scala
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.

```Scala
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.

```Scala
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 :smile:

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:

```Scala
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

```Scala
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.

```Scala
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:

```Scala
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:

```Scala
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:

```Scala
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.

So I hope this article is useful for every programmer.

Thanks for reading!!