packages feed

lambdabot-4.1: Plugin/Dice.hs

--
-- | This module is for throwing dice for e.g. RPGs. (\@dice 3d6+2)
--
-- Copyright Einar Karttunen <ekarttun@cs.helsinki.fi> 2005-04-06.
--
module Plugin.Dice (theModule) where

import Plugin

import Control.Monad                    (replicateM,foldM)
import System.Random                    (randomRIO)
import Text.ParserCombinators.Parsec

PLUGIN Dice

instance Module DiceModule () where
    moduleCmds   _  = ["dice"]
    moduleHelp _ _  = "dice <expr>. Throw random dice. <expr> is of the form 3d6+2."
    process_ _ _ xs = ios (dice xs)

dice :: String -> IO String
dice str = case parse expr "dice" (filter (not.isSpace) str) of
            Left err  -> return . trimError $ err
            Right e   -> do res <- eval e
                            return (brk 30 str++" => "++brk 45 (show res))
           where
            brk n s | length s <= n = s
                    | otherwise     = take (n-3) s ++ "..."
            trimError = concat . intersperse ": " . tail . lines . show

eval :: [(Integer, Integer)] -> IO Integer
eval = foldM ef 0
    where ef acc (v,1) = return (acc+v)
          ef acc (n,d) = if n > 100
                            then do x <- ndRandomIO
                                    let e = n*(d+1)`div`2
                                        e' = fromIntegral (n*(d+1)`mod`2)/2
                                        v = fromIntegral (d*d-1)/12
                                        x' = e' + x * sqrt (fromIntegral n * v)
                                    return (acc + e + round x')
                            else do ls <- replicateM (fromIntegral n)
                                                     (randomRIO (1,d))
                                    return (acc + sum ls)

-- | get a normally distributed random number
ndRandomIO :: IO Double
ndRandomIO = do r   <- randomRIO (0, 1)
                phi <- randomRIO (0, 2*pi)
                let r' = sqrt (-2 * log r)
                return (r' * sin phi)

expr :: CharParser st [(Integer, Integer)]
expr = do res <- primExp `sepBy1` (char '+')
          eof <?> "end"
          return res

primExp :: CharParser st (Integer, Integer)
primExp = do v <- number
             d <- option 1 (char 'd' >> number)
             return (v,d)

number :: CharParser st Integer
number = read `fmap` many1 digit <?> "number"