poker-base-0.1.0.0: src/Poker/Range.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-}
-- | A range of Hole cards.
module Poker.Range
( Freq (..),
Range (..),
rangeFromList,
getDecisionFreqRange,
holdingRangeToShapedRange,
addHoleToShapedRange,
)
where
import Data.Bool (bool)
import Data.Map (Map)
import qualified Data.Map.Strict as Map
#if MIN_VERSION_prettyprinter(1,7,0)
import Prettyprinter
#else
import Data.Text.Prettyprint.Doc
#endif
import Poker.Cards
-- $setup
-- >>> :set -XTypeApplications
-- >>> :set -XOverloadedStrings
-- >>> import Poker.Cards
-- >>> import Poker.Range
-- | A frequency is an unevaluated ratio that indicates how often a decision was
-- made. For example, the value Freq (12, 34) indicates that out of the 34
-- people who faced this decision, 12 chose to make this decision.
data Freq = Freq !Int !Int
deriving (Show, Eq)
instance Monoid Freq where
mempty = Freq 0 0
instance Semigroup Freq where
(Freq l1 r1) <> (Freq l2 r2) = Freq (l1 + l2) (r1 + r2)
-- | A simple wrapper around a 'Map' that uses different instances
-- for Semigroup. 'Range'\'s 'Semigroup' instance combines values at the same keys with '<>'
-- (unlike the 'Map' 'Semigroup' instance from @containers@).
--
-- Note that the 'Range'\'s internal 'Map' is strict.
newtype Range a b = Range
{_range :: Map a b}
deriving (Read, Eq, Show)
-- | Make a Range form a list.
rangeFromList :: Ord a => [(a, b)] -> Range a b
rangeFromList = Range . Map.fromList
-- | >>> mempty @(Range Hole Freq)
-- Range {_range = fromList []}
instance (Ord a, Monoid b) => Monoid (Range a b) where
mempty = Range Map.empty
-- |
-- >>> let left = rangeFromList [("55p" :: ShapedHole, Freq 1 3)]
-- >>> let right = rangeFromList [("55p", Freq 10 32)]
-- >>> left <> right
-- Range {_range = fromList [(Pair Five,Freq 11 35)]}
instance (Ord a, Monoid b) => Semigroup (Range a b) where
Range x <> Range y = Range $ x `uniRange` y
where
uniRange = Map.unionWith (<>)
instance (Pretty a, Pretty b) => Pretty (Range a b) where
pretty ran =
let m = Map.toList $ _range ran
prettyValues =
concatWith (surround comma) $
map (\(c, i) -> pretty c <+> colon <+> pretty i) m
in lbrace <+> prettyValues <+> rbrace
-- | Converts a 'Range' from key to action, to a 'Range' from key to decision
-- frequency, given a predicate that returns 'True' if the action matched the
-- decision.
getDecisionFreqRange ::
Foldable f => (b -> Bool) -> Range a (f b) -> Range a Freq
getDecisionFreqRange p (Range m) =
Range $ Map.map (foldMap (\v -> Freq (bool 0 1 $ p v) 1)) m
-- | Convert from a 'Range' of hole cards to a 'Range' of 'ShapedHole'.
holdingRangeToShapedRange :: Monoid v => Range Hole v -> Range ShapedHole v
holdingRangeToShapedRange (Range r) =
Range $ Map.mapKeysWith (<>) holeToShapedHole r
-- | Add a singleton 'Hole' hand to a 'Range' of 'ShapedHole'.
addHoleToShapedRange :: Num a => a -> Hole -> Range ShapedHole a -> Range ShapedHole a
addHoleToShapedRange n comb (Range r) =
Range $ Map.alter (pure . maybe 0 (+ n)) (holeToShapedHole comb) r