Similar to, but distinct from Poker hand identifier. I'm working towards solving this kata. The below code doesn't print the result yet, and it reads hand strings rather than game strings.
Take 4:
import Data.String
import Data.List
import Data.Ord
data Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine
| Ten | Jack | Queen | King | Ace
deriving (Eq, Ord, Show, Bounded, Enum)
instance Read Rank where
readsPrec _ value =
let tbl = zip "23456789TJQKA" [Two .. Ace]
in case lookup (head value) tbl of
Just r -> [(r, tail value)]
Nothing -> error $ "Invalid rank: " ++ value
data Suit = H | C | D | S deriving (Eq, Ord, Show, Read)
data Card = Card { rank :: Rank, suit :: Suit } deriving (Eq, Ord, Show)
instance Read Card where
readsPrec _ [r, s] = [(Card (read [r]) (read [s]), "")]
readsPrec _ value = error $ "Invalid card: " ++ value
data Hand = Hand { handRank :: HandRank, cards :: [Card] }
deriving (Eq, Show, Ord)
instance Read Hand where
readsPrec _ value =
[(Hand (getHandRank res) res, "")]
where res = reverse . sort . map read $ words value
data HandRank = HighCard [Rank]
| Pair [Rank]
| TwoPair [Rank]
| ThreeOfAKind [Rank]
| Straight [Rank]
| Flush [Rank]
| FullHouse [Rank]
| FourOfAKind [Rank]
| StraightFlush [Rank]
deriving (Eq, Ord, Show)
data GameOutcome = Winner String Hand | Tie deriving (Eq, Ord)
instance Show GameOutcome where
show o = case o of
Winner player hand -> player ++ " wins with " ++ show (handRank hand)
Tie -> "Tie"
isFlush :: [Card] -> Bool
isFlush = (1==) . length . group . map suit
isStraight :: [Card] -> Bool
isStraight cards =
let rs = sort $ map rank cards
run = [(head rs) .. (last rs)]
in rs == run
getHandRank :: [Card] -> HandRank
getHandRank cards =
let ranks = map rank cards
rankGroups = sortByLen $ group ranks
relevantRanks = map (!!0) rankGroups
handRank = case cards of
_ | isFlush cards && isStraight cards -> StraightFlush
| has4 rankGroups -> FourOfAKind
| has3 rankGroups && has2 rankGroups -> FullHouse
| isFlush cards -> Flush
| isStraight cards -> Straight
| has3 rankGroups -> ThreeOfAKind
| countGroupsOf 2 rankGroups == 2 -> TwoPair
| has2 rankGroups -> Pair
| otherwise -> HighCard
in handRank relevantRanks
winner :: Hand -> Hand -> GameOutcome
winner h1 h2 =
case compare h1 h2 of
GT -> Winner "Player 1" h1
LT -> Winner "Player 2" h2
EQ -> Tie
-------------------------------
-- General Utility Functions --
-------------------------------
hasGroupOf :: Int -> [[a]] -> Bool
hasGroupOf n groups = n `elem` map length groups
has4 = hasGroupOf 4
has3 = hasGroupOf 3
has2 = hasGroupOf 2
countGroupsOf :: Int -> [[a]] -> Int
countGroupsOf n groups = length $ filter (\g -> length g == n) groups
sortByLen :: [[a]] -> [[a]]
sortByLen = sortBy (flip $ comparing length)
- Added comparison function
- Fixed a bug relating to improper sorting in some situations (replaced
nub
withrelevantRanks
ingetHandRank
- Ran it through
hlint
My only experience with Haskell so far is some playing around with Parsec
and a few half-read-throughs of WYAS48, so please be obnoxious about style issues.
All feedback welcome, but I would particularly like to ask
- Are there built-ins/better implementations of the "General Utility" functions defined at the bottom?
- Is there a clearer or more succinct way of writing
Read Rank
? - Is there a clearer or more flexible way of writing
getHandRank
, with particular emphasis on closely connecting those predicates with thedata
entry?