{-# LANGUAGE DeriveGeneric, FlexibleInstances, TypeSynonymInstances #-}
#if __GLASGOW_HASKELL__ >= 800
{-# OPTIONS_GHC -Wno-orphans #-}
#endif
module Game.LambdaHack.Common.Dice
(
Dice, diceConst, diceLevel, diceMult, (|*|)
, d, dl, intToDice
, maxDice, minDice, meanDice, reduceDice
, DiceXY(..), maxDiceXY, minDiceXY, meanDiceXY
#ifdef EXPOSE_INTERNAL
, SimpleDice
#endif
) where
import Prelude ()
import Game.LambdaHack.Common.Prelude
import Control.Applicative
import Control.DeepSeq
import Data.Binary
import qualified Data.Char as Char
import Data.Hashable (Hashable)
import qualified Data.IntMap.Strict as IM
import qualified Data.Text as T
import Data.Tuple
import GHC.Generics (Generic)
import Game.LambdaHack.Common.Frequency
type SimpleDice = Frequency Int
normalizeSimple :: SimpleDice -> SimpleDice
normalizeSimple fr = toFreq (nameFrequency fr)
$ map swap $ IM.toAscList $ IM.fromListWith (+)
$ map swap $ runFrequency fr
instance Num SimpleDice where
fr1 + fr2 = normalizeSimple $ liftA2AdditiveName "+" (+) fr1 fr2
fr1 * fr2 =
let frRes = normalizeSimple $ do
n <- fr1
sum $ replicate n fr2
nameRes =
case T.uncons $ nameFrequency fr2 of
_ | nameFrequency fr1 == "0" || nameFrequency fr2 == "0" -> "0"
Just ('d', _) | T.all Char.isDigit $ nameFrequency fr1 ->
nameFrequency fr1 <> nameFrequency fr2
_ -> nameFrequency fr1 <+> "*" <+> nameFrequency fr2
in renameFreq nameRes frRes
fr1 - fr2 = normalizeSimple $ liftA2AdditiveName "-" (-) fr1 fr2
negate = liftAName "-" negate
abs = normalizeSimple . liftAName "abs" abs
signum = normalizeSimple . liftAName "signum" signum
fromInteger n = renameFreq (tshow n) $ pure $ fromInteger n
liftAName :: Text -> (Int -> Int) -> SimpleDice -> SimpleDice
liftAName name f fr =
let frRes = f <$> fr
nameRes = name <> " (" <> nameFrequency fr <> ")"
in renameFreq nameRes frRes
liftA2AdditiveName :: Text
-> (Int -> Int -> Int)
-> SimpleDice -> SimpleDice -> SimpleDice
liftA2AdditiveName name f fra frb =
let frRes = liftA2 f fra frb
nameRes
| nameFrequency fra == "0" =
(if name == "+" then "" else name) <+> nameFrequency frb
| nameFrequency frb == "0" = nameFrequency fra
| otherwise = nameFrequency fra <+> name <+> nameFrequency frb
in renameFreq nameRes frRes
dieSimple :: Int -> SimpleDice
dieSimple n = uniformFreq ("d" <> tshow n) [1..n]
zdieSimple :: Int -> SimpleDice
zdieSimple n = uniformFreq ("z" <> tshow n) [0..n-1]
dieLevelSimple :: Int -> SimpleDice
dieLevelSimple n = uniformFreq ("dl" <> tshow n) [1..n]
zdieLevelSimple :: Int -> SimpleDice
zdieLevelSimple n = uniformFreq ("zl" <> tshow n) [0..n-1]
data Dice = Dice
{ diceConst :: SimpleDice
, diceLevel :: SimpleDice
, diceMult :: !Int
}
deriving (Eq, Ord, Generic)
instance Show Dice where
show Dice{..} = T.unpack $
let rawMult = nameFrequency diceLevel
scaled = if rawMult == "0" then "" else rawMult
signAndMult = case T.uncons scaled of
Just ('-', _) -> scaled
_ -> "+" <+> scaled
in (if | nameFrequency diceLevel == "0" -> nameFrequency diceConst
| nameFrequency diceConst == "0" -> scaled
| otherwise -> nameFrequency diceConst <+> signAndMult)
<+> if diceMult == 1 then "" else "|*|" <+> tshow diceMult
instance Hashable Dice
instance Binary Dice
instance NFData Dice
instance Num Dice where
(Dice dc1 dl1 ds1) + (Dice dc2 dl2 ds2) =
Dice (scaleFreq ds1 dc1 + scaleFreq ds2 dc2)
(scaleFreq ds1 dl1 + scaleFreq ds2 dl2)
(if ds1 == 1 && ds2 == 1 then 1 else
assert `failure` (ds1, ds2, "|*| must be at top level" :: Text))
(Dice dc1 dl1 ds1) * (Dice dc2 dl2 ds2) =
Dice (scaleFreq ds1 dc1 * scaleFreq ds2 dc2)
(scaleFreq ds1 dc1 * scaleFreq ds2 dl2
+ scaleFreq ds1 dl1 * scaleFreq ds2 dc2)
(if ds1 == 1 && ds2 == 1 then 1 else
assert `failure` (ds1, ds2, "|*| must be at top level" :: Text))
(Dice dc1 dl1 ds1) - (Dice dc2 dl2 ds2) =
Dice (scaleFreq ds1 dc1 - scaleFreq ds2 dc2)
(scaleFreq ds1 dl1 - scaleFreq ds2 dl2)
(if ds1 == 1 && ds2 == 1 then 1 else
assert `failure` (ds1, ds2, "|*| must be at top level" :: Text))
negate = affectBothDice negate
abs = affectBothDice abs
signum = affectBothDice signum
fromInteger n = Dice (fromInteger n) 0 1
affectBothDice :: (SimpleDice -> SimpleDice) -> Dice -> Dice
affectBothDice f (Dice dc1 dl1 ds1) = Dice (f dc1) (f dl1) ds1
d :: Int -> Dice
d n = Dice (dieSimple n) 0 1
dl :: Int -> Dice
dl n = Dice 0 (dieLevelSimple n) 1
_z :: Int -> Dice
_z n = Dice (zdieSimple n) 0 1
_zl :: Int -> Dice
_zl n = Dice 0 (zdieLevelSimple n) 1
intToDice :: Int -> Dice
intToDice = fromInteger . fromIntegral
infixl 5 |*|
(|*|) :: Dice -> Int -> Dice
Dice dc1 dl1 ds1 |*| s2 = Dice dc1 dl1 (ds1 * s2)
maxDice :: Dice -> Int
maxDice Dice{..} = (fromMaybe 0 (maxFreq diceConst)
+ fromMaybe 0 (maxFreq diceLevel) `div` 2)
* diceMult
minDice :: Dice -> Int
minDice Dice{..} = (fromMaybe 0 (minFreq diceConst)
+ fromMaybe 0 (minFreq diceLevel) `div` 2)
* diceMult
meanDice :: Dice -> Int
meanDice Dice{..} = (meanFreq diceConst
+ meanFreq diceLevel `div` 2)
* diceMult
reduceDice :: Dice -> Maybe Int
reduceDice de =
let minD = minDice de
in if minD == maxDice de then Just minD else Nothing
data DiceXY = DiceXY !Dice !Dice
deriving (Show, Eq, Ord, Generic)
instance Hashable DiceXY
instance Binary DiceXY
maxDiceXY :: DiceXY -> (Int, Int)
maxDiceXY (DiceXY x y) = (maxDice x, maxDice y)
minDiceXY :: DiceXY -> (Int, Int)
minDiceXY (DiceXY x y) = (minDice x, minDice y)
meanDiceXY :: DiceXY -> (Int, Int)
meanDiceXY (DiceXY x y) = (meanDice x, meanDice y)