The idea is to represent a hand as a list of cards and create a frequency mapping, which can then be used to identify what rank of hand you have and arrange your hand in a way that allows the Ord type class to compare hands of the same rank.
My solution feels a little cumbersome, however this is a lot nicer than anything I could have written imperatively, as poker hand evaluation is a little awkward in general.
card.hs
module Card
(Card(..), Suit(..), Rank(..), rankVal) where
data Card = Card Suit Rank
data Suit =
Spades
| Hearts
| Clubs
| Diamonds
deriving (Show, Eq, Enum, Bounded)
data Rank =
Two
| Three
| Four
| Five
| Six
| Seven
| Eight
| Nine
| Ten
| Jack
| Queen
| King
| Ace
deriving (Show, Eq, Ord, Enum, Bounded)
instance Eq Card where
Card _ rank1 == Card _ rank2 = rank1 == rank2
instance Ord Card where
Card _ rank1 `compare` Card _ rank2 = rank1 `compare` rank2
instance Show Card where
show (Card suit rank) = "(" ++ (show suit) ++ ", " ++ (show rank) ++ ")"
rankVal :: Rank -> Int
rankVal Two = 2
rankVal Three = 3
rankVal Four = 4
rankVal Five = 5
rankVal Six = 6
rankVal Seven = 7
rankVal Eight = 8
rankVal Nine = 9
rankVal Ten = 10
rankVal Jack = 10
rankVal Queen = 10
rankVal King = 10
rankVal Ace = 11
solver.hs
module Hand
(Card(..), Suit(..), Rank(..), compareHands) where
import Card
import Data.List
--TODO Add tests for every function
type Hand = [Card]
-- Cards arranged such that `compare` will return which hand is better
type RelativeRank = [Card]
-- A mapping between an element in a list and it's frequency
-- For example, [1, 2, 2, 2, 2] is [(1,1),(2,4),(2,4),(2,4),(2,4)]
type FreqMapping a = [(a, Int)]
data HandRank =
HighCard
| Pair
| TwoPairs
| ThreeOfKind
| Straight
| Flush
| FullHouse
| FourOfKind
| StraightFlush
| RoyalFlush
deriving (Show, Eq, Ord, Enum, Bounded)
compareHands :: Hand -> Hand -> Ordering
compareHands hand1 hand2 = (handRank1, relativeRank1) `compare` (handRank2, relativeRank2)
where relativeRank1 = computeRelativeRank hand1 handRank1
relativeRank2 = computeRelativeRank hand2 handRank2
handRank1 = computeHandRank hand1
handRank2 = computeHandRank hand2
maxVal :: Hand -> Int
maxVal = foldr (\(Card _ rank) acc -> max acc $ rankVal rank) 0
isStraight :: Hand -> Bool
isStraight = isStraightHelper . sort
isStraightHelper :: Hand -> Bool
isStraightHelper [] = True
isStraightHelper [x] = True
isStraightHelper (card1:card2:xs) = isValidStep && isStraightHelper (card2:xs)
where isValidStep = 1 + rankVal rank1 == rankVal rank2
(Card _ rank1) = card1
(Card _ rank2) = card2
isFlush :: Hand -> Bool
isFlush (x:xs) = (replicate len $ suit x) == (map suit (x:xs))
where suit = (\(Card suit _) -> suit)
len = length (x:xs)
computeHandRank :: Hand -> HandRank
computeHandRank xs
| flush && straight && maxVal xs == 12 = RoyalFlush
| flush && straight = StraightFlush
| freqList == [1, 4, 4, 4, 4] = FourOfKind
| freqList == [2, 2, 3, 3, 3] = FullHouse
| flush = Flush
| straight = Straight
| freqList == [1, 1, 3, 3, 3] = ThreeOfKind
| freqList == [1, 2, 2, 2, 2] = TwoPairs
| freqList == [1, 1, 1, 2, 2] = Pair
| otherwise = HighCard
where straight = isStraight xs
flush = isFlush xs
freqList = sort $ map snd $ computeFreqMapping xs
-- Used to compare hands of the same rank
computeRelativeRank :: Hand -> HandRank -> RelativeRank
computeRelativeRank xs handRank
| handRank == RoyalFlush = []
| handRank == StraightFlush = revSort xs
| handRank == FourOfKind = valsAtFreq 4 freqs ++ valsAtFreq 1 freqs
| handRank == FullHouse = valsAtFreq 3 freqs ++ valsAtFreq 2 freqs
| handRank == Flush = revSort xs
| handRank == Straight = revSort xs
| handRank == ThreeOfKind = valsAtFreq 3 freqs ++ (revSort $ valsAtFreq 1 freqs)
| handRank == TwoPairs = (maximum $ valsAtFreq 2 freqs) : (minimum $ valsAtFreq 2 freqs) : (valsAtFreq 1 freqs)
| handRank == Pair = valsAtFreq 2 freqs ++ (revSort $ valsAtFreq 1 freqs)
| handRank == HighCard = revSort xs
where freqs = computeFreqMapping xs
computeFreqMapping :: (Eq a) => [a] -> FreqMapping a
computeFreqMapping xs = map (\elem -> (elem, elemCount elem xs)) xs
-- Return number of times an element appears in a list
elemCount :: (Eq a) => a -> [a] -> Int
elemCount elem = length . filter (elem==)
--Return set of all values that appear at a given frequency in the freqency mapping
valsAtFreq :: (Ord a) => Int -> FreqMapping a -> [a]
valsAtFreq freq xs = [fst x | x <- xs, snd x == freq]
revSort :: (Ord a) => [a] -> [a]
revSort = reverse . sort
```