module Game.LambdaHack.Utils.Frequency
(
Frequency
, uniformFreq, toFreq
, scaleFreq, filterFreq
, rollFreq, nullFreq, runFrequency
) where
import Control.Monad
import qualified System.Random as R
import Game.LambdaHack.Utils.Assert
data Frequency a = Frequency
{ _name :: String
, runFrequency :: [(Int, a)]
}
deriving Show
instance Monad Frequency where
return x = Frequency "return" [(1, x)]
Frequency name xs >>= f =
Frequency ("bind (" ++ name ++ ")")
[(p * q, y) | (p, x) <- xs,
(q, y) <- runFrequency (f x) ]
instance MonadPlus Frequency where
mplus (Frequency xname xs) (Frequency yname ys) =
Frequency ("mplus (" ++ xname ++ ") (" ++ yname ++ ")")
(xs ++ ys)
mzero = Frequency "mzero" []
instance Functor Frequency where
fmap f (Frequency name xs) = Frequency name (map (\ (p, x) -> (p, f x)) xs)
uniformFreq :: String -> [a] -> Frequency a
uniformFreq name = Frequency name . map (\ x -> (1, x))
toFreq :: String -> [(Int, a)] -> Frequency a
toFreq = Frequency
scaleFreq :: Show a => Int -> Frequency a -> Frequency a
scaleFreq n (Frequency name xs) =
assert (n > 0 `blame` ("negative scale for " ++ name, n, xs)) $
Frequency name (map (\ (p, x) -> (n * p, x)) xs)
filterFreq :: (a -> Bool) -> Frequency a -> Frequency a
filterFreq p (Frequency name l) =
Frequency ("filterFreq (" ++ name ++ ")")
(filter (p . snd) l)
rollFreq :: Show a => Frequency a -> R.StdGen -> (a, R.StdGen)
rollFreq (Frequency name []) _ =
assert `failure` ("choice from an empty frequency: " ++ name)
rollFreq (Frequency name [(n, x)]) _ | n <= 0 =
assert `failure` ("singleton frequency with nothing to pick: " ++ name, n, x)
rollFreq (Frequency _ [(_, x)]) g = (x, g)
rollFreq (Frequency name fs) g =
assert (sumf > 0 `blame` ("frequency with nothing to pick: " ++ name, fs)) $
(frec r fs, ng)
where
sumf = sum (map fst fs)
(r, ng) = R.randomR (1, sumf) g
frec :: Int -> [(Int, a)] -> a
frec m [] = assert `failure` ("impossible", name, fs, m)
frec m ((n, x) : _) | m <= n = x
frec m ((n, _) : xs) = frec (m n) xs
nullFreq :: Frequency a -> Bool
nullFreq fr = null $ runFrequency fr