Haskell でポーカーの役判定処理
麻雀の役判定処理を Haskell で書いてみようかと思って、スモールスタートとしてポーカーの役判定処理を書いてみた。
最初はすべてのカードをデータコンストラクタとして定義する方法を検討していたんだけど、データコンストラクタが多くなりすぎてパターンマッチがしづらくなることがわかった。
カード番号を Int で持つ方法は汎用的なんだけど、番号は 1〜13 までしか存在しないという規約を型で表現できなくなってしまうのが微妙なところ。
たった 5 枚のカードの組み合わせで、ブタ合わせて 10 種類しか役がないポーカーで、こんなに長いコードになってしまうのなら、麻雀は一体どうなってしまうのだろうか・・・?
module Poker ( Card, Hand, HandRank, ranking ) where import Data.List (sort) data Card = S Int | H Int | D Int | C Int deriving (Read, Show, Ord, Eq) data Hand = Hand Card Card Card Card Card deriving (Show, Eq) data HandRank = RoyalStraightFlush | StraightFlush | FourCard | FullHouse | Flush | Straight | ThreeCard | TwoPair | OnePair | HighCard deriving (Show, Read, Eq, Ord, Enum) num :: Card -> Int num (S n) = n num (H n) = n num (D n) = n num (C n) = n sort' :: Hand -> Hand sort' (Hand c1 c2 c3 c4 c5) = Hand (h !! 0) (h !! 1) (h !! 2) (h !! 3) (h !! 4) where h = sort [c1, c2, c3, c4, c5] ranking :: Hand -> HandRank ranking = ranking' . sort' ranking' :: Hand -> HandRank ranking' h = case isRoyalStraightFlush h of True -> RoyalStraightFlush _ -> case isStraightFlush h of True -> StraightFlush _ -> case isFourCard h of True -> FourCard _ -> case isFullHouse h of True -> FullHouse _ -> case isFlush h of True -> Flush _ -> case isStraight h of True -> Straight _ -> case isThreeCard h of True -> ThreeCard _ -> case isTwoPair h of True -> TwoPair _ -> case isOnePair h of True -> OnePair _ -> HighCard isRoyalStraightFlush :: Hand -> Bool isRoyalStraightFlush (Hand (S 1) (S 10) (S 11) (S 12) (S 13)) = True isRoyalStraightFlush (Hand (H 1) (H 10) (H 11) (H 12) (H 13)) = True isRoyalStraightFlush (Hand (D 1) (D 10) (D 11) (D 12) (D 13)) = True isRoyalStraightFlush (Hand (C 1) (C 10) (C 11) (C 12) (C 13)) = True isRoyalStraightFlush _ = False isStraightFlush :: Hand -> Bool isStraightFlush h = case isFlush h of True -> isStraight h _ -> False isFourCard :: Hand -> Bool isFourCard (Hand c1 c2 c3 c4 c5) = case (n1 == n2, n1 == n3, n1 == n4, n1 == n5) of (True, True, True, _) -> True (True, True, _, True) -> True (True, _, True, True) -> True (_, True, True, True) -> True _ -> False where n1 = num c1 n2 = num c2 n3 = num c3 n4 = num c4 n5 = num c5 isFullHouse :: Hand -> Bool isFullHouse = isFullHouse' . sort' isFullHouse' :: Hand -> Bool isFullHouse' (Hand c1 c2 c3 c4 c5) = isFullHouse'' $ sort [num c1, num c2, num c3, num c4, num c5] isFullHouse'' :: [Int] -> Bool isFullHouse'' (n1:n2:n3:n4:n5:[]) = (n1 == n2 && n1 == n3 && n4 == n5) || (n1 == n2 && n3 == n4 && n3 == n5) isFlush :: Hand -> Bool isFlush (Hand (S _) (S _) (S _) (S _) (S _)) = True isFlush (Hand (H _) (H _) (H _) (H _) (H _)) = True isFlush (Hand (D _) (D _) (D _) (D _) (D _)) = True isFlush (Hand (C _) (C _) (C _) (C _) (C _)) = True isFlush _ = False isStraight :: Hand -> Bool isStraight (Hand c1 c2 c3 c4 c5) = isStraight' (num c1) (num c2) (num c3) (num c4) (num c5) isStraight' :: Int -> Int -> Int -> Int -> Int -> Bool isStraight' 1 2 3 4 5 = True isStraight' 2 3 4 5 6 = True isStraight' 3 4 5 6 7 = True isStraight' 4 5 6 7 8 = True isStraight' 5 6 7 8 9 = True isStraight' 6 7 8 9 10 = True isStraight' 7 8 9 10 11 = True isStraight' 8 9 10 11 12 = True isStraight' 9 10 11 12 13 = True isStraight' 1 10 11 12 13 = True isStraight' _ _ _ _ _ = False isThreeCard :: Hand -> Bool isThreeCard (Hand c1 c2 c3 c4 c5) = case (n1 == n2, n1 == n3, n1 == n4, n1 == n5) of (_, _, True, True) -> True (_, True, _, True) -> True (_, True, True, _) -> True (True, _, _, True) -> True (True, _, True, _) -> True (True, True, _, _) -> True _ -> False where n1 = num c1 n2 = num c2 n3 = num c3 n4 = num c4 n5 = num c5 isTwoPair :: Hand -> Bool isTwoPair (Hand c1 c2 c3 c4 c5) = case (n1 == n2, n1 == n3, n1 == n4, n1 == n5) of (True, _, _, _) -> isTwoPair' n3 n4 n5 (_, True, _, _) -> isTwoPair' n2 n4 n5 (_, _, True, _) -> isTwoPair' n2 n3 n5 (_, _, _, True) -> isTwoPair' n2 n3 n4 _ -> case (n2 == n3, n2 == n4, n2 == n5) of (True, _, _) -> n4 == n5 (_, True, _) -> n3 == n5 (_, _, True) -> n3 == n4 _ -> False where n1 = num c1 n2 = num c2 n3 = num c3 n4 = num c4 n5 = num c5 isTwoPair' n1 n2 n3 = case (n1 == n2, n1 == n2) of (True, _) -> True (_, True) -> True _ -> n2 == n3 isOnePair :: Hand -> Bool isOnePair (Hand c1 c2 c3 c4 c5) = case (n1 == n2, n1 == n3, n1 == n4, n1 == n5) of (True, _, _, _) -> True (_, True, _, _) -> True (_, _, True, _) -> True (_, _, _, True) -> True _ -> case (n2 == n3, n2 == n4, n2 == n5) of (True, _, _) -> True (_, True, _) -> True (_, _, True) -> True _ -> case (n3 == n4, n3 == n5) of (True, _) -> True (_, True) -> True _ -> n4 == n5 where n1 = num c1 n2 = num c2 n3 = num c3 n4 = num c4 n5 = num c5