packages feed

poker-0.1.0.0: src/Poker/Deck.hs

-- |
-- Module      : Poker.Deck
-- Description : A deck of cards and its operations.
-- Copyright   : (c) Ghais Issa, 2021
module Poker.Deck
  (
    Suit(..)
  , Rank(..)
  , Card(..)
  , newCard
  , randomCard
  , Deck(..)
  , stdDeck
  , shuffleT
  , shuffle
  , draw
  , draw_
  , draw1
  , draw1_
  , remove
  ) where

import           Data.Bits (shift, (.&.))
import           Data.List (foldl1', (\\))
import           Data.Random (RVar)
import           Data.Random.Distribution.Uniform (uniform)
import           Data.Random.List (shuffleN, shuffleNT)
import           Data.Random.RVar (RVarT)
import           Text.Read (Lexeme (Ident), Read (readPrec), lexP)

data Suit
  = Club
  | Diamond
  | Heart
  | Spade
  deriving stock (Read, Show, Eq, Enum, Ord)

data Rank
  = Two
  | Three
  | Four
  | Five
  | Six
  | Seven
  | Eight
  | Nine
  | Ten
  | Jack
  | Queen
  | King
  | Ace
  deriving stock (Read, Show, Eq, Enum, Ord)

-- | A card is represented as an int where the lower 4 bits representing the
-- and the rest represents the rank ranging from 0-12.
--
-- Use 'newCard' to construct a new card.
newtype Card = Card Int deriving stock (Eq, Ord)

-- | Construct a new card.
newCard :: Rank -> Suit -> Card
newCard rank suite = Card $ fromEnum rank * 4 + fromEnum suite


randomCard :: RVar Card
randomCard = do
  suit   <- uniform 0 3
  rank   <- uniform 0 12
  return $ newCard (toEnum rank) (toEnum suit)


data Deck = Deck !Int ![Card]

-- | construct a full 52-card playing deck. The resulting deck is not shuffled.
stdDeck :: Deck
stdDeck =
  let suits = enumFrom Club
      ranks = enumFrom Two
   in Deck 52 [newCard rank suit | suit <- suits, rank <- ranks]

-- | Shuffle a deck.
shuffleT :: Deck -> RVarT m Deck
shuffleT (Deck n cards) = do
  shuffledCards <- shuffleNT n cards
  return $ Deck n shuffledCards

-- | Shuffle a deck.
shuffle :: Deck -> RVar Deck
shuffle (Deck n cards) = do
  shuffledCards <- shuffleN n cards
  return $ Deck n shuffledCards

-- | Remove a set of cards from a deck, returning the new deck.
remove :: [Card] -> Deck -> Deck
remove cards (Deck n deck) = Deck (n - length cards) (deck \\ cards)

-- | Draw a list of cards from the deck and group them based on the list of hands provided.
--
-- Returns the grouped cards and the remaining deck.
--
-- Arguments that are negative or exceed the length of the deck return Nothing.
--
-- For instance, to simulate a two player Hold'em game, one might wish
-- to draw two cards for each player, and five cards for the community:
--
-- >>> deck <- runRVar (shuffle stdDeck) DevRandom
-- >>> fst . fromJust $ draw [2,2,5] deck
-- [[Ace Club,Queen Club],[Four Diamond,Nine Club],[Jack Heart,King Diamond,Three Heart,Four Club,Two Diamond]]
draw ::
  -- | a list of hand sizes.
  [Int] ->
  -- | The deck.
  Deck ->
  -- | Nothing if the requested number of cards exceeds the deck size, or any of the hands is negative
  -- otherwise returns the hands and the remainder of the deck.
  Maybe ([[Card]], Deck)
draw handSizeLst (Deck n deck)
  | let anyNeg = any (< 0) handSizeLst
     in total > n || total < 0 || 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)
        (hands, remainder) = draw2 handSizeLst ([], deck)
     in Just (hands, Deck (n - total) remainder)
  where
    total = foldl1' (+) handSizeLst


-- | Just like 'draw' but throws away the deck.
draw_ :: [Int] -> Deck -> Maybe [[Card]]
draw_ handSizes (Deck n deck) =
  let f (Just (h, _)) = Just h
      f _             = Nothing
   in f $ draw handSizes (Deck n deck)

-- | The same as 'draw', except draw only one hand of specified size.
draw1 :: Int -> Deck -> Maybe ([Card], Deck)
draw1 handSize (Deck n deck) =
  let f (Just ([h], d)) = Just (h, d)
      f _               = Nothing
   in f $ draw [handSize] (Deck n deck)

-- | Same as 'draw1' but throws away the deck.
draw1_ :: Int -> Deck -> Maybe [Card]
draw1_ handSize (Deck n deck) =
  let f (Just ([h], _)) = Just h
      f _               = Nothing
   in f $ draw [handSize] (Deck n deck)

-- A card is represented as an Int. So we implement Show and Read explicitly.
instance Show Card where
  show (Card c) = show rank ++ " " ++ show suit
    where
      suit :: Suit
      suit = toEnum (c .&. 0x3)
      rank :: Rank
      rank = toEnum $ (c - fromEnum suit) `shift` (-2)

instance Read Card where
  readPrec = do
    (Ident rank) <- lexP
    (Ident suit) <- lexP
    return $ newCard (read rank) (read suit)