poker-0.1.0.0: src/Poker/Holdem/Evaluate.hs
-- |
-- Module : Poker.Holdem.Evaluate
-- Description : Texas Hold'em poker hand evaluator.
-- Copyright : (c) Ghais Issa, 2021
--
-- A Poker Hand Evaluator based on a perfect hash algorithm based
-- on the work of Henry Lee. The original CPP implementation can be
-- found here: [PokerHandEvaluator](https://github.com/HenryRLee/PokerHandEvaluator)
{-# LANGUAGE DerivingVia #-}
module Poker.Holdem.Evaluate
(
HandRank(..)
, evaluate
, evaluateHand
)
where
import Data.Array.Base (unsafeAccumArray, unsafeAt)
import qualified Data.Array.Unboxed as Array
import Data.Bits (shift, (.&.), (.|.))
import Data.Ord
import Poker.Deck
import Poker.Holdem
import qualified Poker.Holdem.Table.DP as Holdem
import qualified Poker.Holdem.Table.Flush as Holdem
import qualified Poker.Holdem.Table.NoFlush as Holdem
import qualified Poker.Holdem.Table.Suit as Holdem
-- | Rank of a hand.
--
-- if @(evaluate hand1) > (evaluate hand2)@ then hand1 is better than hand2
newtype HandRank = HandRank Int deriving newtype (Eq, Show, Read)
deriving newtype (Num)
deriving Ord via (Down Int)
-- | Evaluate a 7-card Texas Hold'em hand returning the rank of the hand.
evaluateHand :: Hand -> HandRank
evaluateHand (Hand (Hole c1 c2) (Community (Flop c3 c4 c5) (Turn c6) (Street c7))) =
evaluate c1 c2 c3 c4 c5 c6 c7
-- | Evaluate a 7-card hand and return the rank of that hand.
evaluate ::
Card -- ^ c1
-> Card -- ^ c2
-> Card -- ^ c3
-> Card -- ^ c4
-> Card -- ^ c5
-> Card -- ^ c6
-> Card -- ^ c7
-> HandRank -- ^ The rank of the hand.
evaluate (Card c1) (Card c2) (Card c3) (Card c4) (Card c5) (Card c6) (Card c7) =
if Holdem.suitsLookup hash > 0 then
HandRank $ handleFlush c1 c2 c3 c4 c5 c6 c7 hash
else
HandRank $ handleNonFlush c1 c2 c3 c4 c5 c6 c7
where
hash = suitHash c1 c2 c3 c4 c5 c6 c7
suitHash :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int
suitHash c1 c2 c3 c4 c5 c6 c7 =
let b1 = suitBit `unsafeAt` c1
b2 = suitBit `unsafeAt` c2
b3 = suitBit `unsafeAt` c3
b4 = suitBit `unsafeAt` c4
b5 = suitBit `unsafeAt` c5
b6 = suitBit `unsafeAt` c6
b7 = suitBit `unsafeAt` c7
in b1 + b2 + b3 + b4 + b5 + b6 + b7
hashQuinary :: Array.UArray Int Int -> Int
hashQuinary q = go 7 0 0
where
go k i s
| i >= 13 = s
| k <= 0 = s
| otherwise =
let sum' = s + Holdem.dpLookup q i k
k' = k - (q `unsafeAt` i)
i' = i + 1
in go k' i' sum'
handleFlush :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int
handleFlush c1 c2 c3 c4 c5 c6 c7 hash =
let
suitBinary :: Array.UArray Int Int
suitBinary = unsafeAccumArray (.|.) 0 (0, 3) [
(c1 .&. 0x3, rankBit `unsafeAt` c1),
(c2 .&. 0x3, rankBit `unsafeAt` c2),
(c3 .&. 0x3, rankBit `unsafeAt` c3),
(c4 .&. 0x3, rankBit `unsafeAt` c4),
(c5 .&. 0x3, rankBit `unsafeAt` c5),
(c6 .&. 0x3, rankBit `unsafeAt` c6),
(c7 .&. 0x3, rankBit `unsafeAt` c7)]
idx = suitBinary `unsafeAt` (Holdem.suitsLookup hash - 1)
in Holdem.flushLookup idx
handleNonFlush :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int
handleNonFlush c1 c2 c3 c4 c5 c6 c7 =
let
idxs :: [(Int, Int)]
idxs = [ (c1 `shift` (-2), 1),
(c2 `shift` (-2), 1),
(c3 `shift` (-2), 1),
(c4 `shift` (-2), 1),
(c5 `shift` (-2), 1),
(c6 `shift` (-2), 1),
(c7 `shift` (-2), 1)
]
quinary :: Array.UArray Int Int
quinary = unsafeAccumArray (+) 0 (0, 12) idxs
hash = hashQuinary quinary
in Holdem.noFlushLookup hash
rankBit :: Array.UArray Int Int
rankBit = Array.listArray (0, 51)
[ 0x1, 0x1, 0x1, 0x1,
0x2, 0x2, 0x2, 0x2,
0x4, 0x4, 0x4, 0x4,
0x8, 0x8, 0x8, 0x8,
0x10, 0x10, 0x10, 0x10,
0x20, 0x20, 0x20, 0x20,
0x40, 0x40, 0x40, 0x40,
0x80, 0x80, 0x80, 0x80,
0x100, 0x100, 0x100, 0x100,
0x200, 0x200, 0x200, 0x200,
0x400, 0x400, 0x400, 0x400,
0x800, 0x800, 0x800, 0x800,
0x1000, 0x1000, 0x1000, 0x1000
]
suitBit :: Array.UArray Int Int
suitBit =
Array.listArray (0,51)
[ 0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200,
0x1, 0x8, 0x40, 0x200
]