hedra-0.1: src/Hedra.hs
{-# LANGUAGE InstanceSigs #-}
-- | Generating dice rolls.
-- The functions in this module use the global random generator
-- described in "System.Random#globalrng".
module Hedra
( Die(..)
, d, f
, roll, showRoll, printRoll
)
where
import Control.Monad (replicateM)
import Data.List (intercalate)
import System.Random (randomRIO)
-- $setup
-- >>> import System.Random (mkStdGen, setStdGen)
-- >>> setStdGen (mkStdGen 0)
-- | Variants of dice.
data Die
-- | A die with the given number of sides. The number is assumed to be positive.
= Sides Int
-- | Two 10-sided dice, one for tens and one for ones. Two zeros are interpreted as 100.
| Percentile
-- | A die with the values -1, 0 and +1.
| Fudge
-- | Only for using 'Num' literals as 'Die' values, all other methods throw an error.
-- The literal @100@ corresponds to percentile dice,
-- all other literals are dice with that many sides.
instance Num Die where
fromInteger :: Integer -> Die
fromInteger n =
case n of
100 -> Percentile
_ -> Sides (fromInteger n)
(+) = error "Num instance for Die not implemented"
(-) = error "Num instance for Die not implemented"
(*) = error "Num instance for Die not implemented"
abs = error "Num instance for Die not implemented"
signum = error "Num instance for Die not implemented"
-- | An alias of 'printRoll' to be used in the REPL.
--
-- >>> 2 `d` 8
-- 10 | 4 6
-- >>> 1 `d` 20
-- 4
d :: Int -> Die -> IO ()
d = printRoll
-- | An alias of 'Fudge' to be used in the REPL.
--
-- >>> 4 `d` f
-- 0 | [+] [+] [-] [-]
f :: Die
f = Fudge
-- | Generates a dice roll as a list of individual rolls.
--
-- >>> roll 2 8
-- [4,6]
-- >>> roll 1 20
-- [4]
-- >>> roll 1 Percentile
-- [39]
-- >>> roll 4 Fudge
-- [0,0,-1,0]
roll
:: Int -- ^ The number of dice to roll, must be at least 0.
-> Die -- ^ The variant of dice to roll.
-> IO [Int] -- ^ The list of individual die rolls.
roll count die
| count < 0 = error "Must roll at least zero dice."
| otherwise = replicateM count oneRoll
where
oneRoll :: IO Int
oneRoll = case die of
Sides sides | sides < 1 -> error "Dice must have at least one side."
Sides sides -> randomRIO (1, sides)
Percentile -> randomRIO (1, 100)
Fudge -> randomRIO ((-1), 1)
-- | Generates a dice roll as a string.
--
-- >>> showRoll 2 8
-- "10 | 4 6"
-- >>> showRoll 1 20
-- "4"
-- >>> showRoll 1 Percentile
-- "39 | 30 9"
-- >>> showRoll 4 Fudge
-- "-1 | [ ] [ ] [-] [ ]"
showRoll
:: Int -- ^ The number of dice to roll, must be at least 0.
-> Die -- ^ The variant of dice to roll.
-> IO String -- ^ The pretty-printed dice roll.
showRoll count die = do
rolls <- roll count die
let rollSum = sum rolls
rollStrings = map showOneRoll rolls
showSum = case die of
Percentile -> True
_ -> count > 1
str =
(if showSum then show rollSum ++ " | " else "")
++ intercalate " " rollStrings
pure str
where
showOneRoll :: Int -> String
showOneRoll n =
case die of
Sides _ ->
show n
Percentile ->
case n `divMod` 10 of
(10, 0) -> "00 0"
(0, ones) -> unwords ["00", show ones]
(tens, ones) -> unwords [show (tens * 10), show ones]
Fudge ->
case n of
-1 -> "[-]"
0 -> "[ ]"
1 -> "[+]"
_ -> error ("Fudge die result out of range: " ++ show n)
-- | Generates and prints and a dice roll.
--
-- >>> printRoll 2 8
-- 10 | 4 6
-- >>> printRoll 1 20
-- 4
-- >>> printRoll 1 Percentile
-- 39 | 30 9
-- >>> printRoll 4 Fudge
-- -1 | [ ] [ ] [-] [ ]
printRoll
:: Int -- ^ The number of dice to roll, must be at least 0.
-> Die -- ^ The variant of dice to roll.
-> IO ()
printRoll count die =
showRoll count die >>= putStrLn