packages feed

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