poker-base-0.1.0.0: test/Test/Poker/Cards.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Test.Poker.Cards where
import Control.Applicative
import Control.Monad
#if MIN_VERSION_prettyprinter(1,7,0)
import Prettyprinter
#else
import Data.Text.Prettyprint.Doc
#endif
import Data.Functor
import Data.List.Extra
import Data.Maybe
import Data.String (IsString (fromString))
import Data.Text (Text)
import Poker
import Test.Hspec
spec_rankToChr :: SpecWith ()
spec_rankToChr = do
it "rankToChr" $ (allRanks <&> rankToChr) `shouldBe` "23456789TJQKA"
spec_chrToRank :: SpecWith ()
spec_chrToRank = do
it "chrToRank <$> \"23456789TJQKA\"" $ (fromJust . chrToRank <$> "23456789TJQKA") `shouldBe` allRanks
it "chrToRank '1' == Nothing" $ chrToRank '1' `shouldBe` Nothing
spec_suitToChr :: SpecWith ()
spec_suitToChr = do
it "suitToChr" $ (allSuits <&> suitToChr) `shouldBe` "cdhs"
spec_chrToSuit :: SpecWith ()
spec_chrToSuit = do
it "chrToSuit <$> \"cdhs\"" $ (fromJust . chrToSuit <$> "cdhs") `shouldBe` allSuits
it "chrToSuit '1' == Nothing" $ chrToSuit '1' `shouldBe` Nothing
spec_cardToShortTxt :: SpecWith ()
spec_cardToShortTxt = it "cardToShortTxt" $ forM_ cardCases \(txt, card) -> cardToShortTxt card `shouldBe` txt
spec_cardFromShortTxt :: SpecWith ()
spec_cardFromShortTxt = do
it "cardFromShortTxt returns Just for all cards" $ forM_ cardCases \(txt, card) -> cardFromShortTxt txt `shouldBe` Just card
it "cardFromShortTxt \"Ac\" == Just (Card Ace Club)" $ cardFromShortTxt "Ac" `shouldBe` Just (Card Ace Club)
it "cardFromShortTxt \"Acd\" == Nothing" $ cardFromShortTxt "Acd" `shouldBe` Nothing
it "cardFromShortTxt \"AcAd\" == Nothing" $ cardFromShortTxt "AcAd" `shouldBe` Nothing
cardCases :: [(Text, Card)]
cardCases =
[ ("As", Card Ace Spade),
("Ah", Card Ace Heart),
("Ad", Card Ace Diamond),
("Ac", Card Ace Club),
("Ks", Card King Spade),
("Kh", Card King Heart),
("Kd", Card King Diamond),
("Kc", Card King Club),
("Qs", Card Queen Spade),
("Qh", Card Queen Heart),
("Qd", Card Queen Diamond),
("Qc", Card Queen Club),
("Js", Card Jack Spade),
("Jh", Card Jack Heart),
("Jd", Card Jack Diamond),
("Jc", Card Jack Club),
("Ts", Card Ten Spade),
("Th", Card Ten Heart),
("Td", Card Ten Diamond),
("Tc", Card Ten Club),
("9s", Card Nine Spade),
("9h", Card Nine Heart),
("9d", Card Nine Diamond),
("9c", Card Nine Club),
("8s", Card Eight Spade),
("8h", Card Eight Heart),
("8d", Card Eight Diamond),
("8c", Card Eight Club),
("7s", Card Seven Spade),
("7h", Card Seven Heart),
("7d", Card Seven Diamond),
("7c", Card Seven Club),
("6s", Card Six Spade),
("6h", Card Six Heart),
("6d", Card Six Diamond),
("6c", Card Six Club),
("5s", Card Five Spade),
("5h", Card Five Heart),
("5d", Card Five Diamond),
("5c", Card Five Club),
("4s", Card Four Spade),
("4h", Card Four Heart),
("4d", Card Four Diamond),
("4c", Card Four Club),
("3s", Card Three Spade),
("3h", Card Three Heart),
("3d", Card Three Diamond),
("3c", Card Three Club),
("2s", Card Two Spade),
("2h", Card Two Heart),
("2d", Card Two Diamond),
("2c", Card Two Club)
]
spec_mkHole :: SpecWith ()
spec_mkHole = do
let aceS = Card Ace Spade
let kingD = Card King Diamond
it "mkHole" $
mkHole aceS kingD `shouldSatisfy` \case
Just (UnsafeHole c1 c2) | c1 == aceS, c2 == kingD -> True
_ -> False
it "mkHole order doesn't matter" $
mkHole aceS kingD `shouldBe` mkHole kingD aceS
it "fail" $ let c = Card Two Club in mkHole c c `shouldBe` Nothing
it "order doesn't matter" $
allCardPairs
`shouldSatisfy` all
(\(c1, c2) -> mkHole c1 c2 == mkHole c2 c1)
it "non-equal cards always succeed" $
allCardPairs
`shouldSatisfy` all
(\(c1, c2) -> c1 == c2 || isJust (mkHole c1 c2))
where
allCardPairs = [(c1, c2) | c1 <- allCards, c2 <- allCards]
spec_mkShapedHole :: SpecWith ()
spec_mkShapedHole = do
it "mkPair" $
mkPair Ace `shouldSatisfy` \case
Pair Ace -> True
_ -> False
it "mkSuited success" $
mkSuited Ace King
`shouldSatisfy` ( \case
Just (UnsafeSuited Ace King) -> True
_ -> False
)
it "mkSuited wrong order succeeds" $
mkSuited King Ace
`shouldSatisfy` ( \case
Just (UnsafeSuited Ace King) -> True
_ -> False
)
it "mkSuited failure" $ mkSuited King King `shouldBe` Nothing
it "mkOffsuit success" $
mkOffsuit Ace King
`shouldSatisfy` ( \case
Just (UnsafeOffsuit Ace King) -> True
_ -> False
)
it "mkOffsuit wrong order succeeds" $
mkOffsuit King Ace
`shouldSatisfy` ( \case
Just (UnsafeOffsuit Ace King) -> True
_ -> False
)
it "mkOffsuit failure" $ mkOffsuit King King `shouldBe` Nothing
spec_prettyCard :: SpecWith ()
spec_prettyCard = do
it "Ac" $ show (pretty (Card Ace Club)) `shouldBe` "Ac"
it "2h" $ show (pretty (Card Two Heart)) `shouldBe` "2h"
spec_cardIsString :: SpecWith ()
spec_cardIsString = do
it "Ac is Card Ace Club" $ "Ac" `shouldBe` Card Ace Club
it "2h is Card Two Heart" $ "2h" `shouldBe` Card Two Heart
let failCase = failingIsString @Card
mapM_ failCase ["Ac2h", "AA", "cc"]
spec_holeIsString :: SpecWith ()
spec_holeIsString = do
it "AcKh" $ "AcKh" `shouldBe` fromJust (mkHole (Card Ace Club) (Card King Heart))
it "AcKh == KhAc" $ ("AcKh" :: Hole) `shouldBe` "KhAc"
let failCase = failingIsString @Hole
mapM_ failCase ["AAKh", "AcKK", "Ac", "AcK", "AcKhQd"]
spec_prettyHole :: SpecWith ()
spec_prettyHole = do
it "AcKh" $ show (pretty . fromJust $ mkHole (Card Ace Club) (Card King Heart)) `shouldBe` "AcKh"
it "5s2d" $ show (pretty . fromJust $ mkHole (Card Five Spade) (Card Two Diamond)) `shouldBe` "5s2d"
spec_prettyShapedHole :: SpecWith ()
spec_prettyShapedHole = do
it "Offsuit" $ show (pretty (mkOffsuit Ace Two)) `shouldBe` "A2o"
it "Pair" $ show (pretty (mkPair Ace)) `shouldBe` "AAp"
it "Suited" $ show (pretty (mkSuited Ace Two)) `shouldBe` "A2s"
spec_isStringShapedHole :: SpecWith ()
spec_isStringShapedHole = do
it "AKo" $ "AKo" `shouldBe` unsafeOffsuit Ace King
it "AKo == KAo" $ ("AKo" :: ShapedHole) `shouldBe` "KAo"
it "AKs" $ "AKs" `shouldBe` unsafeSuited Ace King
it "AKs == KAs" $ ("AKs" :: ShapedHole) `shouldBe` "KAs"
it "AAp" $ "AAp" `shouldBe` Pair Ace
let failCase = failingIsString @ShapedHole
mapM_
failCase
["AKp", "AKf", "AFo", "FKo", "Kp", "AA", "p"]
spec_holeToShaped :: SpecWith ()
spec_holeToShaped = do
let doCase hole shaped = it (hole <> " => " <> shaped) $ holeToShapedHole (fromString hole) `shouldBe` fromString shaped
doCase "AcKd" "AKo"
doCase "AcKc" "AKs"
doCase "AcAs" "AAp"
spec_shapedHoleToHoles :: SpecWith ()
spec_shapedHoleToHoles = do
mkCase "Pair" "55p" ["5d5c", "5h5c", "5s5c", "5h5d", "5s5d", "5s5h"]
mkCase "Suited" "AKs" ["AcKc", "AdKd", "AhKh", "AsKs"]
mkCase "Offsuit" "QTo" offSuitExpected
it "Sanity test OffSuit" $ offSuitExpected `shouldSatisfy` not . anySame
where
offSuitExpected =
[ "QcTd",
"QcTh",
"QcTs",
"QdTc",
"QdTh",
"QdTs",
"QhTc",
"QhTd",
"QhTs",
"QsTc",
"QsTd",
"QsTh"
]
mkCase name combo expected =
it name $
shapedHoleToHoles (fromString combo)
`shouldBe` fromString
<$> expected
spec_toUnicode :: SpecWith ()
spec_toUnicode = do
it "encode Suit" $
suitToUnicode
<$> (enumerate @Suit)
`shouldBe` "\9827\9830\9829\9824" -- TODO fix tasty-discover bug wrt unicode
it "fromUnicode . toUnicode forms isomorphism" $
fromJust
. suitFromUnicode
. suitToUnicode
<$> enumerate @Suit
`shouldBe` enumerate @Suit
it "fromUnicode fail" $ suitFromUnicode 'b' `shouldBe` Nothing
spec_all :: SpecWith ()
spec_all = do
describe "enumerate" $ do
it "Rank" $ allRanks `shouldBe` allRanksExpected
it "Suit" $ allSuits `shouldBe` allSuitsExpected
it "Card" $ allCards `shouldBe` allCardsExpected
it "number of Holes should be 1326" $ length allHoles `shouldBe` 1326
it "allHoles are unique" $ nub allHoles `shouldBe` allHoles
it "allHoles Hole cards are valid" $
allHoles `forM_` flip shouldSatisfy (\(UnsafeHole c1 c2) -> c1 > c2)
it "number of ShapedHoles should be 169" $
length allShapedHoles
`shouldBe` 169
it "allShapedHoles are unique" $ nub allShapedHoles `shouldBe` allShapedHoles
it "allShapedHoles" $ allShapedHoles `shouldBe` allShapedHolesExpected
it "allShapedHoles ShapedHole ranks are valid" $
allShapedHoles
`forM_` flip
shouldSatisfy
( \case
(Pair _) -> True
(UnsafeOffsuit r1 r2) -> r1 > r2
(UnsafeSuited r1 r2) -> r1 > r2
)
where
allSuitsExpected = [Club, Diamond, Heart, Spade]
allRanksExpected =
[ Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King,
Ace
]
allCardsExpected = liftA2 Card allRanks allSuits
allShapedHolesExpected = ["AAp", "AKs", "AQs", "AJs", "ATs", "A9s", "A8s", "A7s", "A6s", "A5s", "A4s", "A3s", "A2s", "AKo", "KKp", "KQs", "KJs", "KTs", "K9s", "K8s", "K7s", "K6s", "K5s", "K4s", "K3s", "K2s", "AQo", "KQo", "QQp", "QJs", "QTs", "Q9s", "Q8s", "Q7s", "Q6s", "Q5s", "Q4s", "Q3s", "Q2s", "AJo", "KJo", "QJo", "JJp", "JTs", "J9s", "J8s", "J7s", "J6s", "J5s", "J4s", "J3s", "J2s", "ATo", "KTo", "QTo", "JTo", "TTp", "T9s", "T8s", "T7s", "T6s", "T5s", "T4s", "T3s", "T2s", "A9o", "K9o", "Q9o", "J9o", "T9o", "99p", "98s", "97s", "96s", "95s", "94s", "93s", "92s", "A8o", "K8o", "Q8o", "J8o", "T8o", "98o", "88p", "87s", "86s", "85s", "84s", "83s", "82s", "A7o", "K7o", "Q7o", "J7o", "T7o", "97o", "87o", "77p", "76s", "75s", "74s", "73s", "72s", "A6o", "K6o", "Q6o", "J6o", "T6o", "96o", "86o", "76o", "66p", "65s", "64s", "63s", "62s", "A5o", "K5o", "Q5o", "J5o", "T5o", "95o", "85o", "75o", "65o", "55p", "54s", "53s", "52s", "A4o", "K4o", "Q4o", "J4o", "T4o", "94o", "84o", "74o", "64o", "54o", "44p", "43s", "42s", "A3o", "K3o", "Q3o", "J3o", "T3o", "93o", "83o", "73o", "63o", "53o", "43o", "33p", "32s", "A2o", "K2o", "Q2o", "J2o", "T2o", "92o", "82o", "72o", "62o", "52o", "42o", "32o", "22p"]
spec_freshDeck :: SpecWith ()
spec_freshDeck = it "freshDeck" $ freshDeck `shouldBe` unsafeDeck allCards
-- Check that a call to IsString fails for the given type. Values are forced via
-- value's Show instance
failingIsString :: forall a. (IsString a, Show a) => String -> SpecWith ()
failingIsString str = it (str <> " fails") $ print (fromString @a str) `shouldThrow` anyErrorCall