{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}
module Game.LambdaHack.Core.Dice
(
Dice, AbsDepth(..), castDice, d, dL, z, zL, intToDice, minDice, maxDice
, infsupDice, supDice, infDice, meanDice, reduceDice
, DiceXY(..), supDiceXY, infDiceXY, meanDiceXY
) where
import Prelude ()
import Game.LambdaHack.Core.Prelude
import Data.Binary
import Data.Hashable (Hashable)
import GHC.Generics (Generic)
data Dice =
DiceI Int
| DiceD Int Int
| DiceDL Int Int
| DiceZ Int Int
| DiceZL Int Int
| DicePlus Dice Dice
| DiceTimes Dice Dice
| DiceNegate Dice
| DiceMin Dice Dice
| DiceMax Dice Dice
deriving (Eq, Generic)
instance Show Dice where
show = stripOuterParens . showDiceWithParens
stripOuterParens :: String -> String
stripOuterParens s@('(' : rest) = case uncons $ reverse rest of
Just (')', middle) -> reverse middle
_ -> s
stripOuterParens s = s
showDiceWithParens :: Dice -> String
showDiceWithParens = sh
where
sh dice1 = case dice1 of
DiceI k -> show k
DiceD n k -> show n ++ "d" ++ show k
DiceDL n k -> show n ++ "dL" ++ show k
DiceZ n k -> show n ++ "z" ++ show k
DiceZL n k -> show n ++ "zL" ++ show k
DicePlus d1 (DiceNegate d2) -> wrapInParens $ sh d1 ++ "-" ++ sh d2
DicePlus (DiceNegate d1) d2 -> wrapInParens $ "-" ++ sh d1 ++ "+" ++ sh d2
DicePlus d1 (DicePlus d2 d3) -> sh $ DicePlus (DicePlus d1 d2) d3
DicePlus (DicePlus d1 d2) d3 ->
wrapInParens $ stripOuterParens (sh $ DicePlus d1 d2) ++ "+" ++ sh d3
DicePlus d1 d2 -> wrapInParens $ sh d1 ++ "+" ++ sh d2
DiceTimes d1 d2 -> wrapInParens $ sh d1 ++ "*" ++ sh d2
DiceNegate d1 -> wrapInParens $ "-" ++ sh d1
DiceMin d1 d2 -> wrapInParens $ "min" ++ sh d1 ++ sh d2
DiceMax d1 d2 -> wrapInParens $ "max" ++ sh d1 ++ sh d2
wrapInParens :: String -> String
wrapInParens "" = ""
wrapInParens t = "(" <> t <> ")"
instance Binary Dice
instance Num Dice where
d1 + d2 = DicePlus d1 d2
d1 * d2 = DiceTimes d1 d2
d1 - d2 = d1 + DiceNegate d2
negate = DiceNegate
abs = undefined
signum = undefined
fromInteger n = DiceI (fromInteger n)
newtype AbsDepth = AbsDepth Int
deriving (Show, Eq, Ord, Hashable, Binary)
castDice :: forall m. Monad m
=> ((Int, Int) -> m Int)
-> AbsDepth -> AbsDepth -> Dice -> m Int
castDice randomR (AbsDepth lvlDepth) (AbsDepth maxDepth) dice = do
let !_A = assert (lvlDepth >= 0 && lvlDepth <= maxDepth
`blame` "invalid depth for dice rolls"
`swith` (lvlDepth, maxDepth)) ()
castNK n start k = if start == k then return $! n * k else do
let f !acc 0 = return acc
f acc count = do
r <- randomR (start, k)
f (acc + r) (count - 1)
f 0 n
scaleL k = (k * max 1 lvlDepth) `divUp` max 1 maxDepth
castD :: Dice -> m Int
castD dice1 = case dice1 of
DiceI k -> return k
DiceD n k -> castNK n 1 k
DiceDL n k -> scaleL <$> castNK n 1 k
DiceZ n k -> castNK n 0 (k - 1)
DiceZL n k -> scaleL <$> castNK n 0 (k - 1)
DicePlus d1 d2 -> do
k1 <- castD d1
k2 <- castD d2
return $! k1 + k2
DiceTimes d1 d2 -> do
k1 <- castD d1
k2 <- castD d2
return $! k1 * k2
DiceNegate d1 -> do
k <- castD d1
return $! negate k
DiceMin d1 d2 -> do
k1 <- castD d1
k2 <- castD d2
return $! min k1 k2
DiceMax d1 d2 -> do
k1 <- castD d1
k2 <- castD d2
return $! max k1 k2
castD dice
d :: Int -> Int -> Dice
d n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))
$ DiceD n k
dL :: Int -> Int -> Dice
dL n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))
$ DiceDL n k
z :: Int -> Int -> Dice
z n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))
$ DiceZ n k
zL :: Int -> Int -> Dice
zL n k = assert (n > 0 && k > 0 `blame` "die must be positive" `swith` (n, k))
$ DiceZL n k
intToDice :: Int -> Dice
intToDice = DiceI
minDice :: Dice -> Dice -> Dice
minDice = DiceMin
maxDice :: Dice -> Dice -> Dice
maxDice = DiceMax
infsupDice :: Dice -> (Int, Int)
infsupDice dice1 = case dice1 of
DiceI k -> (k, k)
DiceD n k -> (n, n * k)
DiceDL n k -> (1, n * k)
DiceZ n k -> (0, n * (k - 1))
DiceZL n k -> (0, n * (k - 1))
DicePlus d1 d2 ->
let (infD1, supD1) = infsupDice d1
(infD2, supD2) = infsupDice d2
in (infD1 + infD2, supD1 + supD2)
DiceTimes (DiceI k) d2 ->
let (infD2, supD2) = infsupDice d2
in if k >= 0 then (k * infD2, k * supD2) else (k * supD2, k * infD2)
DiceTimes d1 (DiceI k) ->
let (infD1, supD1) = infsupDice d1
in if k >= 0 then (infD1 * k, supD1 * k) else (supD1 * k, infD1 * k)
DiceTimes d1 d2 ->
let (infD1, supD1) = infsupDice d1
(infD2, supD2) = infsupDice d2
options = [infD1 * infD2, infD1 * supD2, supD1 * supD2, supD1 * infD2]
in (minimum options, maximum options)
DiceNegate d1 ->
let (infD1, supD1) = infsupDice d1
in (negate supD1, negate infD1)
DiceMin d1 d2 ->
let (infD1, supD1) = infsupDice d1
(infD2, supD2) = infsupDice d2
in (min infD1 infD2, min supD1 supD2)
DiceMax d1 d2 ->
let (infD1, supD1) = infsupDice d1
(infD2, supD2) = infsupDice d2
in (max infD1 infD2, max supD1 supD2)
supDice :: Dice -> Int
supDice = snd . infsupDice
infDice :: Dice -> Int
infDice = fst . infsupDice
meanDice :: Dice -> Double
meanDice dice1 = case dice1 of
DiceI k -> fromIntegral k
DiceD n k -> fromIntegral (n * (k + 1)) / 2
DiceDL n k -> fromIntegral (n * (k + 1)) / 4
DiceZ n k -> fromIntegral (n * k) / 2
DiceZL n k -> fromIntegral (n * k) / 4
DicePlus d1 d2 -> meanDice d1 + meanDice d2
DiceTimes d1 d2 -> meanDice d1 * meanDice d2
DiceNegate d1 -> negate $ meanDice d1
DiceMin d1 d2 -> min (meanDice d1) (meanDice d2)
DiceMax d1 d2 -> max (meanDice d1) (meanDice d2)
reduceDice :: Dice -> Maybe Int
reduceDice d1 =
let (infD1, supD1) = infsupDice d1
in if infD1 == supD1 then Just infD1 else Nothing
data DiceXY = DiceXY Dice Dice
deriving (Show, Generic)
instance Binary DiceXY
supDiceXY :: DiceXY -> (Int, Int)
supDiceXY (DiceXY x y) = (supDice x, supDice y)
infDiceXY :: DiceXY -> (Int, Int)
infDiceXY (DiceXY x y) = (infDice x, infDice y)
meanDiceXY :: DiceXY -> (Double, Double)
meanDiceXY (DiceXY x y) = (meanDice x, meanDice y)