general-games 0.2.0 → 0.3.0
raw patch · 5 files changed
+184/−66 lines, 5 filesdep +MonadRandomdep +random-shufflePVP ok
version bump matches the API change (PVP)
Dependencies added: MonadRandom, random-shuffle
API changes (from Hackage documentation)
+ Game.Game.Poker: AceHigh :: AceRank
+ Game.Game.Poker: AceLow :: AceRank
+ Game.Game.Poker: data AceRank
+ Game.Game.Poker: instance GHC.Classes.Eq Game.Game.Poker.AceRank
+ Game.Game.Poker: instance GHC.Show.Show Game.Game.Poker.AceRank
+ Game.Game.Poker: mkBestHand :: [PlayingCard] -> Maybe PokerHand
+ Game.Game.Poker: mkConsecutiveRanks :: [PlayingCard] -> Maybe ([PlayingCard], AceRank)
+ Game.Game.Poker: orderOfAceRank :: AceRank -> Order
+ Game.Implement.Card: shuffle :: (Card c, MonadRandom m) => [c] -> m [c]
- Game.Game.Poker: Straight :: PokerHandType
+ Game.Game.Poker: Straight :: AceRank -> PokerHandType
- Game.Game.Poker: StraightFlush :: PokerHandType
+ Game.Game.Poker: StraightFlush :: AceRank -> PokerHandType
- Game.Implement.Card: class (Enum c, Eq c, Ord c, Bounded c) => Card c where fullDeck = [minBound .. maxBound] dedupe l = nub l draw handSizeLst deck | let total = (foldl1' (+) handSizeLst) anyNeg = (length (filter (\ n -> n < 0) handSizeLst)) > 0 in (total > (length deck)) || (total < 1) || anyNeg = Nothing | otherwise = let draw2 [] (houtput, doutput) = ((reverse houtput), doutput) draw2 (nToTake : hst) (handOutput, deckOutput) = let newHand = take nToTake deckOutput newDeck = drop nToTake deckOutput in draw2 hst (newHand : handOutput, newDeck) in Just (draw2 handSizeLst ([], deck))
+ Game.Implement.Card: class (Enum c, Eq c, Ord c, Bounded c) => Card c where fullDeck = [minBound .. maxBound] dedupe l = nub l shuffle deck = shuffleM deck draw handSizeLst deck | let total = (foldl1' (+) handSizeLst) anyNeg = (length (filter (\ n -> n < 0) handSizeLst)) > 0 in (total > (length deck)) || (total < 1) || anyNeg = Nothing | otherwise = let draw2 [] (houtput, doutput) = ((reverse houtput), doutput) draw2 (nToTake : hst) (handOutput, deckOutput) = let newHand = take nToTake deckOutput newDeck = drop nToTake deckOutput in draw2 hst (newHand : handOutput, newDeck) in Just (draw2 handSizeLst ([], deck))
Files
- general-games.cabal +6/−3
- src/Game/Game/Poker.hs +54/−21
- src/Game/Implement/Card.hs +37/−3
- src/Game/Implement/Card/Standard.hs +2/−2
- test/Spec.hs +85/−37
general-games.cabal view
@@ -1,5 +1,5 @@ name: general-games-version: 0.2.0+version: 0.3.0 synopsis: Library supporting simulation of a number of games homepage: https://github.com/cgorski/general-games bug-reports: https://github.com/cgorski/general-games/issues@@ -8,7 +8,7 @@ author: Christopher A. Gorski maintainer: cgorski@cgorski.org copyright: 2017 Christopher A. Gorski-category: Games, Poker+category: Game, Poker build-type: Simple extra-source-files: README.md cabal-version: >=1.10@@ -29,6 +29,8 @@ , Game.Game.Poker build-depends: base >= 4.7 && < 5+ , random-shuffle+ , MonadRandom default-language: Haskell2010 ghc-options: -Wall @@ -40,7 +42,8 @@ , general-games , HUnit , hspec- ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ , MonadRandom+ ghc-options: -fhpc -Wall -threaded -rtsopts -with-rtsopts=-N default-language: Haskell2010 source-repository head
src/Game/Game/Poker.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-}+ module Game.Game.Poker where @@ -7,35 +8,54 @@ import Game.Implement.Card.Standard import Game.Implement.Card.Standard.Poker -import Data.List (tails,nub,find) --, sortBy, nub, find)-import Data.Maybe (isJust)+import Data.List (tails,nub,find) +import Data.Maybe (isJust, fromJust, catMaybes) type RankHand = [PlayingCard] type KickerHand = [PlayingCard] data RankKicker = RankHand KickerHand deriving(Eq,Show) +data AceRank = AceHigh | AceLow deriving (Eq, Show)++orderOfAceRank :: AceRank -> Order+orderOfAceRank AceHigh = AceHighRankOrder+orderOfAceRank AceLow = AceLowRankOrder+ data PokerHandType = HighCard | Pair | TwoPair | ThreeOfAKind - | Straight + | Straight AceRank | Flush | FullHouse | FourOfAKind - | StraightFlush + | StraightFlush AceRank | RoyalFlush deriving(Eq,Show) data PokerHandSplit = PokerHandType RankKicker deriving(Eq,Show) data PokerHand = PokerHand PokerHandType [PlayingCard] deriving(Eq,Show) --- mkBestHand :: S.Hand -> Maybe PokerHand--- mkBestHand hand--- | isPokerHandSize hand = Nothing--- | otherwise = mkHighCard hand-+mkBestHand :: [PlayingCard] -> Maybe PokerHand+mkBestHand hand =+ let checks =+ [mkHighCard hand+ ,mkPair hand+ ,mkTwoPair hand+ ,mkThreeOfAKind hand+ ,mkStraight hand+ ,mkFlush hand+ ,mkFullHouse hand+ ,mkFourOfAKind hand+ ,mkStraightFlush hand+ ,mkRoyalFlush hand]+ cat = catMaybes checks+ in + if length cat == 0+ then Nothing+ else Just $ cat !! 0 isSameSuit :: [PlayingCard] -> Bool isSameSuit hand =@@ -150,15 +170,27 @@ | isJust $ mkThreeOfAKind hand = True | otherwise = False +mkConsecutiveRanks :: [PlayingCard] -> Maybe ([PlayingCard], AceRank)+mkConsecutiveRanks hand =+ let consecHigh h = (hasConsecutiveRanks AceHighRankOrder h)+ consecLow h = (hasConsecutiveRanks AceLowRankOrder h)+ f h2+ | consecHigh h2 = Just (sortCardsBy AceHighRankOrder h2, AceHigh)+ | consecLow h2 = Just (sortCardsBy AceLowRankOrder h2, AceLow)+ | otherwise = Nothing+ in f hand+ + mkStraight :: [PlayingCard] -> Maybe PokerHand mkStraight hand | isPokerHandSize hand =- if ((hasConsecutiveRanks AceHighRankOrder hand)- || (hasConsecutiveRanks AceLowRankOrder hand))- && (not $ isRoyalFlush hand)- && (not $ isStraightFlush hand)- then Just (PokerHand Straight (sortCardsBy AceHighRankOrder hand))+ let consecRanks = mkConsecutiveRanks hand+ isConsecRanks = isJust consecRanks in+ if isConsecRanks+ && (not $ isRoyalFlush hand)+ && (not $ isStraightFlush hand)+ then Just (PokerHand (Straight $ snd $ fromJust consecRanks) hand) else Nothing | otherwise = Nothing @@ -173,7 +205,7 @@ if (isSameSuit hand) && (not $ isRoyalFlush hand) && (not $ isStraightFlush hand) - then Just (PokerHand Flush (sortCardsBy AceHighRankOrder hand))+ then Just (PokerHand Flush hand) else Nothing | otherwise = Nothing @@ -212,12 +244,13 @@ mkStraightFlush :: [PlayingCard] -> Maybe PokerHand mkStraightFlush hand | isPokerHandSize hand =- if (isSameSuit hand)- && ((hasConsecutiveRanks AceHighRankOrder hand)- || (hasConsecutiveRanks AceLowRankOrder hand))- && (not $ isRoyalFlush hand)- then Just (PokerHand StraightFlush hand)- else Nothing + let consecRanks = mkConsecutiveRanks hand+ isConsecRanks = isJust consecRanks in+ if isConsecRanks+ && (isSameSuit hand)+ && (not $ isRoyalFlush hand)+ then Just (PokerHand (Straight $ snd $ fromJust consecRanks) hand)+ else Nothing | otherwise = Nothing isStraightFlush :: [PlayingCard] -> Bool
src/Game/Implement/Card.hs view
@@ -1,16 +1,42 @@ {-# LANGUAGE MultiParamTypeClasses #-}-+-- |+-- Module : Game.Implement.Card+-- Copyright : (c) 2017 Christopher A. Gorski+-- License : MIT+-- Maintainer : Christopher A. Gorski <cgorski@cgorski.org>+--+-- The Game.Implement.Card module provides fundamental operations for a deck of cards. module Game.Implement.Card+ (+ Card (..)+ , ValuedCard (..)+ , OrderedCard (..)+ , OrderedValuedCard (..)+ ) where +import Control.Monad.Random+import System.Random.Shuffle (shuffleM) import Data.List (nub, maximumBy, minimumBy, sortBy, foldl1') +-- |+-- Represents a physical card with no order and no value.+-- Inherited Enum, Eq, Ord and Bounded typeclasses are used to+-- distingish cards for the purposes of manipulation within lists.+-- Game value functions are provided by other typeclasses. class (Enum c, Eq c, Ord c, Bounded c) => Card c where+ -- |+ -- Return all cards in a list. Cards will appear at most once. Order is not guaranteed.+ --+ -- >>> fullDeck :: [PlayingCard]+ -- [Ace of Clubs,Two of Clubs,Three of Clubs,Four of Clubs,Five of Clubs,Six of Clubs,Seven of Clubs,Eight of Clubs,Nine of Clubs,Ten of Clubs,Jack of Clubs,Queen of Clubs,King of Clubs,Ace of Diamonds,Two of Diamonds,Three of Diamonds,Four of Diamonds,Five of Diamonds,Six of Diamonds,Seven of Diamonds,Eight of Diamonds,Nine of Diamonds,Ten of Diamonds,Jack of Diamonds,Queen of Diamonds,King of Diamonds,Ace of Hearts,Two of Hearts,Three of Hearts,Four of Hearts,Five of Hearts,Six of Hearts,Seven of Hearts,Eight of Hearts,Nine of Hearts,Ten of Hearts,Jack of Hearts,Queen of Hearts,King of Hearts,Ace of Spades,Two of Spades,Three of Spades,Four of Spades,Five of Spades,Six of Spades,Seven of Spades,Eight of Spades,Nine of Spades,Ten of Spades,Jack of Spades,Queen of Spades,King of Spades] fullDeck :: [c] dedupe :: [c] -> [c] draw :: [Int] -> [c] -> Maybe ([[c]],[c])+ shuffle :: MonadRandom m => [c] -> m [c] fullDeck = [minBound .. maxBound] dedupe l = nub l+ shuffle deck = shuffleM deck draw handSizeLst deck | let total = (foldl1' (+) handSizeLst)@@ -24,13 +50,18 @@ newDeck = drop nToTake deckOutput in draw2 hst (newHand:handOutput, newDeck) in Just (draw2 handSizeLst ([],deck))- -+-- |+-- Represents a playing card with a game value. For instance,+-- a standard playing card with a type representing+-- rank and suit. class (Card c) => ValuedCard c v where toValue :: c -> v toValueLst :: [c] -> [v] toValueLst l = map toValue l +-- |+-- Orderings independent of a specific value+-- type of a Card. class (Card c) => OrderedCard c o where highestCardBy :: o -> [c] -> c lowestCardBy :: o -> [c] -> c@@ -40,7 +71,10 @@ lowestCardBy o cl = minimumBy (compareCardBy o) cl sortCardsBy o cl = sortBy (compareCardBy o) cl + class (OrderedCard c o) => OrderedValuedCard c o vt where+ -- |+ -- Return an Int based on a card, an ordering and a value type. toOrderedValue :: o -> vt -> c -> Int
src/Game/Implement/Card/Standard.hs view
@@ -47,10 +47,10 @@ instance Enum PlayingCard where fromEnum (PlayingCard r s) =- (fromEnum r)+((fromEnum s)*nRanks)+ ((fromEnum s)*nRanks)+(fromEnum r) toEnum n = let r = n `mod` nRanks- s = n `mod` 4+ s = n `div` nRanks in (PlayingCard (toEnum r) (toEnum s))
test/Spec.hs view
@@ -1,9 +1,9 @@+import Control.Monad.Random import Test.Hspec import Game.Game.Poker import Game.Implement.Card import Game.Implement.Card.Standard - allHandsCount :: Int allHandsCount = length allPossibleHands allHandsCountExpected :: Int@@ -80,42 +80,90 @@ [PlayingCard Three Clubs, PlayingCard Four Clubs]) +confirmDisjoint :: (Int, Bool)+confirmDisjoint =+ let mfunc1 hand = [mkRoyalFlush hand,+ mkStraightFlush hand,+ mkFourOfAKind hand,+ mkFullHouse hand,+ mkFlush hand,+ mkStraight hand,+ mkThreeOfAKind hand,+ mkTwoPair hand,+ mkPair hand,+ mkHighCard hand]+ maybem (Just _) = 1+ maybem Nothing = 0+ countJust hand = sum $ map maybem $ mfunc1 hand+ allSums = map countJust allPossibleHands+ collect _ (outsum, False) = (outsum, False)+ collect (x:xs) (outsum, _) =+ collect xs (x+outsum, if x==0 || x==1 then True else False)+ collect [] output = output + in+ collect allSums (0, True)++isUnique :: Eq a => [a] -> Bool+isUnique lst = f lst True where+ f _ False = False+ f [] result = result+ f (x:xs) _ = if x `elem` xs then f xs False else f xs True++shuffledDeck :: RandomGen g => Rand g [PlayingCard]+shuffledDeck = shuffle $ fullDeck+ main :: IO ()-main = hspec $ do- describe "Game.Implement.Card instance" $ do- it "returns drawn hands from a deck, plus the remaining deck" $ do- (draw drawDeckSizes drawDeck) `shouldBe` drawDeckExpectedOutput- it "returns Nothing when trying to return more cards than in deck" $ do- (draw drawDeckSizesFail drawDeck) `shouldBe` Nothing- it "returns Nothing when trying to return negative cards" $ do- (draw drawDeckSizesFailNeg drawDeck) `shouldBe` Nothing+main =+ do+ randdecks <- evalRandIO $ replicateM 10000 shuffledDeck; - describe "Game.Implement.Card.Standard.Poker.isRoyalFlush" $ do- it "confirms that [AH, QH, KH, JH, TH] is a Royal Flush" $ do- (isRoyalFlush royalFlush) `shouldBe` True- it "confirms that [AH, QH, 8H, JH, TH] is not a Royal Flush" $ do- (isRoyalFlush royalFlushNot) `shouldBe` False- describe "Game.Implement.Card.Standard.Poker allPossibleHands / isHand functions" $ do- it "confirms the total number of poker hands" $ do- allHandsCount `shouldBe` allHandsCountExpected- it "confirms the total number of royal flushes" $ do- (length allRoyalFlush) `shouldBe` allRoyalFlushCountExpected- it "confirms the total number of straight flushes" $ do- (length allStraightFlush) `shouldBe` allStraightFlushCountExpected- it "confirms the total number of four-of-a-kinds" $ do- (length allFourOfAKind) `shouldBe` allFourOfAKindCountExpected- it "confirms the total number of full houses" $ do- (length allFullHouse) `shouldBe` allFullHouseCountExpected- it "confirms the total number of flushes" $ do- (length allFlush) `shouldBe` allFlushCountExpected- it "confirms the total number of straights" $ do- (length allStraight) `shouldBe` allStraightCountExpected- it "confirms the total number of three-of-a-kinds" $ do- (length allThreeOfAKind) `shouldBe` allThreeOfAKindCountExpected- it "confirms the total number of two-pairs" $ do- (length allTwoPair) `shouldBe` allTwoPairCountExpected- it "confirms the total number of pairs" $ do- (length allPair) `shouldBe` allPairCountExpected- it "confirms the total number of high card hands" $ do- (length allHighCard) `shouldBe` allHighCardCountExpected+ hspec $ do+ describe "Game.Implement.Card.draw (PlayingCard)" $ do+ it "returns drawn hands from a deck, plus the remaining deck" $ do+ (draw drawDeckSizes drawDeck) `shouldBe` drawDeckExpectedOutput+ it "returns Nothing when trying to return more cards than in deck" $ do+ (draw drawDeckSizesFail drawDeck) `shouldBe` Nothing+ it "returns Nothing when trying to return negative cards" $ do+ (draw drawDeckSizesFailNeg drawDeck) `shouldBe` Nothing++ describe "Game.Implement.Card.fullDeck (PlayingCard)" $ do+ it "returns 52 cards" $ do+ length (fullDeck :: [PlayingCard]) `shouldBe` 52+ it "returns unique cards" $ do+ isUnique (fullDeck :: [PlayingCard]) `shouldBe` True++ describe "Game.Implement.Card.shuffle (PlayingCard)" $ do+ it "returns 10000 different fullDeck shuffles using the global random generator" $ do+ (isUnique randdecks) `shouldBe` True+ + describe "Game.Implement.Card.Standard.Poker.isRoyalFlush" $ do+ it "confirms that [AH, QH, KH, JH, TH] is a Royal Flush" $ do+ (isRoyalFlush royalFlush) `shouldBe` True+ it "confirms that [AH, QH, 8H, JH, TH] is not a Royal Flush" $ do+ (isRoyalFlush royalFlushNot) `shouldBe` False+ describe "Game.Implement.Card.Standard.Poker allPossibleHands / mkHand / isHand functions" $ do+ it "confirms that sets of each hand are disjoint and that total count correct" $ do+ confirmDisjoint `shouldBe` (allHandsCountExpected, True)+ it "confirms the total number of poker hands" $ do+ allHandsCount `shouldBe` allHandsCountExpected+ it "confirms the total number of royal flushes" $ do+ (length allRoyalFlush) `shouldBe` allRoyalFlushCountExpected+ it "confirms the total number of straight flushes" $ do+ (length allStraightFlush) `shouldBe` allStraightFlushCountExpected+ it "confirms the total number of four-of-a-kinds" $ do+ (length allFourOfAKind) `shouldBe` allFourOfAKindCountExpected+ it "confirms the total number of full houses" $ do+ (length allFullHouse) `shouldBe` allFullHouseCountExpected+ it "confirms the total number of flushes" $ do+ (length allFlush) `shouldBe` allFlushCountExpected+ it "confirms the total number of straights" $ do+ (length allStraight) `shouldBe` allStraightCountExpected+ it "confirms the total number of three-of-a-kinds" $ do+ (length allThreeOfAKind) `shouldBe` allThreeOfAKindCountExpected+ it "confirms the total number of two-pairs" $ do+ (length allTwoPair) `shouldBe` allTwoPairCountExpected+ it "confirms the total number of pairs" $ do+ (length allPair) `shouldBe` allPairCountExpected+ it "confirms the total number of high card hands" $ do+ (length allHighCard) `shouldBe` allHighCardCountExpected