Similar to, but distinct from this. 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
nubwithrelevantRanksingetHandRank - 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 thedataentry?