packages feed

poker-0.1.0.0: src/Poker/Holdem/Simulate.hs

-- |
-- Module      : Poker.Holdem.Simulate
-- Description : Monte Carlo simulation of a poker holdem game.
-- Copyright   : (c) Ghais Issa, 2021
--
-- Uses Monte Carlo Methods to determine the probability for each player in a game winning.
-- The simulation randomize each of the unknown cards in the game, this could include any
-- number of players' cards as well as flop, turn and river.
module Poker.Holdem.Simulate
  (
    Player(..)
  , Game(..)
  , simulate
  ) where

import           Data.Random (RVar, RandomSource, runRVar)

import           Control.Monad.State
import           Data.List (transpose)

import           Data.Random.Source.DevRandom (DevRandom (DevRandom))
import           Poker.Deck
import qualified Poker.Deck as Deck (shuffle)
import           Poker.Holdem
import           Poker.Holdem.Evaluate (HandRank, evaluate)

-- | A player can have 0, 1, 2 known cards.
data Player = Player
  {
    card1 :: Maybe Card
  , card2 :: Maybe Card
  }

-- | An abstraction that represents a poker game, with some unknowns.
--This allows us to simulate a game from any possible state.
data Game = Game
  {
    players :: [Player]      -- ^ Players.
  , flop    :: Maybe Flop    -- ^ The flop if known. Nothing otherwise.
  , turn    :: Maybe Turn    -- ^ The turn if known. Nothing otherwise.
  , street  :: Maybe Street  -- ^ The street if known. Nothing otherwise.
  }

-- | Run a Monte Carlo simulation of a game returning the probability of winning for each player.
simulate :: (RandomSource m DevRandom) =>
     Int        -- ^ Number of trajectories.
  -> Game         -- ^ State of the game before simulation.
  -> m [Double] -- ^ Probability for each player winning the game.
simulate n game = do
  gameHands <- replicateM n $ simulateWinners game
  return $ map ((/fromIntegral n) . sum) (transpose gameHands)

dealtCards :: Game -> [Card]
dealtCards Game{..} = let playerCards = concatMap dealtHands players
                          dealtHands (Player Nothing Nothing)     = []
                          dealtHands (Player (Just c1) Nothing)   = [c1]
                          dealtHands (Player Nothing (Just c2))   = [c2]
                          dealtHands (Player (Just c1) (Just c2)) = [c1, c2]
                          flopCards = case flop of
                            (Just (Flop c1 c2 c3)) -> [c1, c2, c3]
                            _                      -> []
                          turnCard = case turn of
                            (Just (Turn c)) -> [c]
                            _               -> []
                          streetCard = case street of
                            (Just (Street c)) -> [c]
                            _                 -> []
                      in playerCards ++  flopCards ++ turnCard ++ streetCard

completeHands :: Game -> StateT Deck Maybe [[Card]]
completeHands Game{..} = do
  playersCards <- mapM getPlayersCards players
  (Community (Flop c3 c4 c5) (Turn c6) (Street c7)) <- getCommunityCards flop turn street
  return [[c1, c2, c3, c4, c5, c6, c7] | (Hole c1 c2) <- playersCards]

getPlayersCards :: Player -> StateT Deck Maybe Hole
getPlayersCards player = do
  deck <- get
  (cards, deck') <- lift $ draw1 (neededCards player) deck
  put deck'
  return (hole player cards)
  where neededCards (Player Nothing Nothing)   = 2
        neededCards (Player (Just _) Nothing)  = 1
        neededCards (Player Nothing (Just _))  = 1
        neededCards (Player (Just _) (Just _)) = 0
        hole (Player (Just c1) (Just c2)) []   = Hole  c1 c2
        hole (Player (Just c1) Nothing) [c2]   = Hole c1 c2
        hole (Player Nothing (Just c2)) [c1]   = Hole c1 c2
        hole (Player Nothing Nothing) [c1, c2] = Hole c1 c2
        hole _ _                               = undefined


getCommunityCards :: Maybe Flop -> Maybe Turn -> Maybe Street -> StateT Deck Maybe Community
getCommunityCards Nothing _ _ = do
  deck <- get
  ([c1, c2, c3, c4, c5], deck') <- lift $ draw1 5 deck
  put deck'
  return (Community (Flop c1 c2 c3) (Turn c4) (Street c5))

getCommunityCards (Just (Flop c1 c2 c3)) Nothing _ = do
  deck <- get
  ([c4, c5], deck') <- lift $ draw1 2 deck
  put deck'
  return (Community (Flop c1 c2 c3) (Turn c4) (Street c5))

getCommunityCards (Just (Flop c1 c2 c3)) (Just(Turn c4)) Nothing = do
  deck <- get
  ([c5], deck') <- lift $ draw1 1 deck
  put deck'
  return (Community (Flop c1 c2 c3) (Turn c4) (Street c5))
getCommunityCards (Just (Flop c1 c2 c3)) (Just(Turn c4)) (Just (Street c5)) = do
  return (Community (Flop c1 c2 c3) (Turn c4) (Street c5))



playerHands :: Game -> RVar [[Card]]
playerHands game = do
  deck <- Deck.shuffle (gameDeck game)
  case evalStateT (completeHands game) deck of
    (Just cards) -> return cards
    _            -> return []

gameDeck :: Game -> Deck
gameDeck game = remove (dealtCards game) stdDeck



winners :: [HandRank] -> [Bool]
winners scores = map (== minRank) scores where
  minRank = maximum scores

averageScore :: [Bool] -> [Double]
averageScore winnerList = map (\x -> if x then 1/fromIntegral numWinners else 0) winnerList where
  numWinners = length (filter (== True) winnerList)


simulateOne :: (RandomSource m DevRandom) => Game -> m [([Card], HandRank)]
simulateOne game = do
  cards <- runRVar (playerHands game) DevRandom
  let scores = map evaluate' cards
  return $ zip cards scores
  where evaluate' [c1, c2, c3, c4, c5, c6, c7] = evaluate c1 c2 c3 c4 c5 c6 c7
        evaluate' _                            = undefined

simulateWinners :: (RandomSource m DevRandom) => Game -> m [Double]
simulateWinners game = do
  scores <- map snd <$> simulateOne game
  let gameWinners = winners scores
  return $ averageScore gameWinners