One of my favourite ways of improving my coding is by working on Katas. Katas are small coding problems that are used to practise a technique. The concept derives from Japanese martial arts where students continuously repeat a certain sequence to refine their technique. There is an early Playful Python article that goes into this concept more

The point of a software kata is not to arrive at a "correct" solution, but as a playground to experiment with different approaches. Since we just learned about the multi-value monad in the previous article, how about we use it to solve the Blackjack scoring kata?

## The Kata

One of the key aspects of the game of Blackjack is to determine the score for a hand of cards.

• Cards from `2` to `10` are worth the points equal to their face value
• The cards Jack `J`, Queen `Q` and King `K` are worth 10 points each
• The Ace `Ace` can be worth either 1 point, or 11 points. Usually the higher the score is better, but if your score crosses 21 then you lose (its called going "bust"). So you want to select the points for the `A` in such a way that it is as high as possible without crossing 21

Here are some examples of hands and their scores:

• `2`, `5`, `7`, `2` - 16 points, just add up all the numbers
• `2`, `5`, `7`, `10` - bust
• `2`, `5`, `7`, `A` - 15 points. Here if we take `A` as 11 then the hand will bust. So we take `A` as a 1
• `2`, `A`, `A` - 14 points. We take the first `A` as 11 points. Taking the second `A` as 11 will bust, so we consider the second `A` as 1 point
• `A`, `A`, `A` - 13 points

Generally in blackjack you can only consider one `A` as 11 as any more will definitely bust. However if we keep the bust threshold at a higher value, say 25, then there could be situations where more than one `A` could be counted as 11.

So here is the problem statement

Given a hand of cards, and a bust threshold, determine the score of the hand

We are going to solve this problem in two ways: one using functions (this article), and the second one using classes (next article). We will use the multi-value monad in both approaches.

For reference, here is the code we derived last time for implementing the multi-value monad.

``````from itertools import chain

class MultiValue:
def __init__(self, values):
self.values = set(values)

def map(self, fn):
out = {fn(val) for val in self.values}
return MultiValue(out)

def flatmap(self, fn):
out = (fn(val).values for val in self.values)
return MultiValue(chain(*out))``````

Now let us see the two approaches, starting with the functional approach

## The Functional Approach

Okay, so we have a hand of cards in a list like this: `[2, 5, 7, 'A']`. We need to loop through these and sum them up.

The tricky part is the ace which could be counted as a 1 or 11. Given that it has two values, it makes sense to represent card values as `MultiValue`. Lets start with a function to get the value of a card.

``````def get_card_value(card):
if card == "A":
return MultiValue({1, 11})
elif card in ("J", "Q", "K"):
return MultiValue({10})
return MultiValue({card})
``````

Next, we need to be able to add up the card values, so we need a function to take two `MultiValue` and add them up, like this: `MultiValue({1, 2}) + MultiValue({10, 20})`

Lets build up to that step by step. We start with a function that adds two `int`

``````def add(a: int, b: int) -> int:
return a + b``````

That function is quite straightforward. I've added type hints to make it clear what data types are involved. Actually, we don't even need to write this function ourselves. The `operator` package in python already has it.

``from operator import add``

### Adding a MultiValue and an int

Now lets move on to a function to add a `MultiValue` with an `int`. We can do that using the `map` method on the `MultiValue`.

The catch is that the `map` method needs a function that takes one argument. Our `add` function takes two parameters. We can fix this by partially applying the first parameter. Here is how it works

``````from functools import partial

print(plus_2(10)) # will add 2 to give 12
print(plus_2(6)) # 8``````

`partial` allows us to take `add` and set the first argument ahead of time. The return value `plus_2` is a function that takes the remaining argument.

And since `plus_2` only takes one argument, it can be applied via `map`. Here is an example

``````mv = MultiValue({4, 10})
mv.map(plus_2) # MultiValue({6, 12})``````

and the generalised function

``````def add_mv_and_int(mv: MultiValue, b: int) -> MultiValue:

Note the type signature. This function returns a `MultiValue` as an output

Now we have to add two multivalues. Here is an example: `MultiValue({1, 2}) + MultiValue({10, 20})` which will equal `MultiValue({11, 21, 12, 22})`

To do this, we apply the `add_mv_and_int` function. Since that function returns a `MultiValue` as an output, we need to use `flatmap` here instead of `map`. Also, `add_mv_and_int` takes two parameters, so we need to use `partial` as before. Here is an example

``````a = MultiValue({1, 2})
b = MultiValue({10, 20})
a.flatmap(partial(add_mv_and_int, b)) # MultiValue({11, 21, 12, 22})``````

and the generic function

``````def add_multivalue(a: MultiValue, b: MultiValue) -> MultiValue:

### Solving the problem

Alright now that we have a function for adding two `MultiValue`, how do we add all the values? Well, we can start with an identity value (`MultiValue({0})`) and adding it with the first number, take that sum and add it with the second, then take that sum and add it with the third and so on.

This kind of operation is called a `reduce` and Python has it in the `functools` module.

``````from functools import reduce

def hand_value(hand):
values = [get_card_value(card) for card in hand]

Now that we have all the possible totals it is simple to complete the function. Just filter out the values that don't bust and find the maximum one. That is the score of the hand.

Here is what the final code looks like

``````from functools import partial
from functools import reduce

def add_mv_and_int(mv: MultiValue, b: int) -> MultiValue:

def add_multivalue(a: MultiValue, b: MultiValue) -> MultiValue:

def get_card_value(card):
if card == "A":
return MultiValue({1, 11})
elif card in ("J", "Q", "K"):
return MultiValue({10})
return MultiValue({card})

def hand_value(hand):
if len(hand) == 0:
return 0
values = [get_card_value(card) for card in hand]
no_bust = {value for value in total.values if value <= 21}
try:
return max(no_bust)
except ValueError:
return None # all possible scores are bust``````

The code in `hand_value` reads almost like pseudocode

1. If the hand is empty the score is zero
2. Get the values of the cards
4. Get the possibilities that do not bust
5. Find the highest of the possibilities. If no possibilities, return `None`

We don't need to handle the complexity that some variables are multi-valued, making the code clean and easy to read.