packages feed

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 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