Kata: C** de chouette - multi-players
In the previous log, we have started a coding kata based on a dices game called Cul de chouette.
I have mentioned that I have skipped the multi-players to stay simple.
Let's pick some rules as the following ones:
- La Chouette Velute: owls are equal and their sum is equal to the bottom, the double of the square of the bottom, points are given to the first player clapping their hands and yelling "Pas mou le caillou !"
- La Suite: three following dices (e.g. 3-4-5), except 1-2-3 which is a Velute (18 points), the last player banging his/her fist on the table yelling "Grelotte ça picote !" loose 10 points
We can start by defining Players and adding it to score, as follows:
newtype Player
= Player {unPlayer :: Text}
deriving newtype (Eq, Ord, Show, IsString)
score p o0 o1 b
| diceInt o0 + diceInt o1 == diceInt b = (p, ScorePoints $ Points $ 2 * diceInt b * diceInt b)
| o0 == o1 && o0 == b = (p, ScorePoints $ Points $ diceInt o0 * 10 + 40)
| o0 == o1 || o0 == b = (p, ScorePoints $ Points $ diceInt o0 * diceInt o0)
| o1 == b = (p, ScorePoints $ Points $ diceInt o1 * diceInt o1)
| sort [o0, o1, b] == take 3 [(minimum [o0, o1, b]) ..] = (p, ScorePoints $ Points (-10))
| otherwise = (p, ScoreGrelotting)
The next step is to define Actions as follows:
data Actions = PasMouLeCailloux | GrelotteCaPicotte
deriving stock (Eq, Show)
And finally to integrate both:
score p o0 o1 b actions
| diceInt o0 + diceInt o1 == diceInt b =
case lookup PasMouLeCailloux $ map swap actions of
Just p' -> (p', ScorePoints $ Points $ 2 * diceInt b * diceInt b)
| o0 == o1 && o0 == b = (p, ScorePoints $ Points $ diceInt o0 * 10 + 40)
| o0 == o1 || o0 == b = (p, ScorePoints $ Points $ diceInt o0 * diceInt o0)
| o1 == b = (p, ScorePoints $ Points $ diceInt o1 * diceInt o1)
| sort [o0, o1, b] == take 3 [(minimum [o0, o1, b]) ..] =
case lookup GrelotteCaPicotte $ reverse $ map swap actions of
Just p' -> (p', ScorePoints $ Points (-10))
| otherwise = (p, ScoreGrelotting)
You can notice that the two new case are non-exhaustive, there is a case called "Bévue" (- 10 points) when a player does not respect the rules (e.g. use GrelotteCaPicotte instead PasMouLeCailloux, or when not needed).
But nothing when no action is given, let's produce multiple scores:
score p o0 o1 b actions
| diceInt o0 + diceInt o1 == diceInt b =
let errors = actionError <$> filter ((/= PasMouLeCailloux) . snd) actions
in case lookup PasMouLeCailloux $ map swap actions of
Just p' -> (p', ScorePoints $ Points $ 2 * diceInt b * diceInt b) : errors
Nothing -> errors
| o0 == o1 && o0 == b = (p, ScorePoints $ Points $ diceInt o0 * 10 + 40) : allActionsErrors
| o0 == o1 || o0 == b = (p, ScorePoints $ Points $ diceInt o0 * diceInt o0) : allActionsErrors
| o1 == b = (p, ScorePoints $ Points $ diceInt o1 * diceInt o1) : allActionsErrors
| sort [o0, o1, b] == take 3 [(minimum [o0, o1, b]) ..] =
let errors = actionError <$> filter ((/= GrelotteCaPicotte) . snd) actions
in case lookup GrelotteCaPicotte $ reverse $ map swap actions of
Just p' -> (p', ScorePoints $ Points (-10)) : errors
Nothing -> errors
| otherwise = (p, ScoreGrelotting) : allActionsErrors
where
allActionsErrors = actionError <$> actions
actionError = fmap (const $ ScorePoints $ Points (-10))
It's a bit hairy, but that's it for the basis, next time we will see additional rules which involve dealing with time.