Main.hs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. module Main where
  2. import qualified Control.Monad.IO.Class as Monad
  3. import qualified Control.Monad as Monad
  4. import GHC.Exts (groupWith)
  5. import qualified Graphics.Rendering.Cairo as Cairo
  6. import qualified Graphics.Rendering.Pango as Pango
  7. data P = P Int Int deriving (Eq, Ord)
  8. instance Show P where
  9. show (P n t) = show n ++ " in " ++ show t
  10. times :: P -> P -> P
  11. P a at `times` P b bt = P (a * b) (at * bt)
  12. combine :: [P] -> P
  13. combine ps =
  14. let tots = [ t | P _ t <- ps ]
  15. newTot = foldr lcm 1 tots
  16. newNums = [ if t == newTot then n else error "???" | P n t <- ps ]
  17. in P (sum newNums) newTot
  18. newtype Prob a = Prob { unprob :: [(a, P)] } deriving (Eq, Show)
  19. instance Functor Prob where
  20. fmap f (Prob xs) = Prob [(f x, p) | (x, p) <- xs]
  21. instance Applicative Prob where
  22. pure x = Prob [(x, P 1 1)]
  23. Prob fs <*> Prob xs = Prob [(f x, pf `times` px) | (f, pf) <- fs, (x, px) <- xs]
  24. instance Monad Prob where
  25. Prob xs >>= f =
  26. Prob [ (y, p `times` p')
  27. | (x, p) <- xs
  28. , (y, p') <- unprob (f x)
  29. ]
  30. die :: Int -> Prob Int
  31. die n = Prob [(d, P 1 n) | d <- [1..n]]
  32. data Result = Success | Partial | Failure deriving (Eq, Show, Ord)
  33. judge :: Int -> Result
  34. judge n
  35. | n <= 6 = Failure
  36. | n <= 9 = Partial
  37. | otherwise = Success
  38. normalize :: (Ord a) => Prob a -> Prob a
  39. normalize (Prob xs) =
  40. Prob [(fst (head rs), combine (map snd rs)) | rs <- groupWith fst xs ]
  41. roll :: Int -> Prob Result
  42. roll m = normalize $ do
  43. a <- die 6
  44. b <- die 6
  45. return (judge (a + b + m))
  46. modRoll :: Int -> Prob Result
  47. modRoll m = normalize $ do
  48. a <- die 20
  49. let r = a + m
  50. return (if r < 10 then Failure else if r < 16 then Partial else Success)
  51. find :: Result -> Prob Result -> Double
  52. find r (Prob rs) = case [ fromIntegral n / fromIntegral t | (r', P n t) <- rs, r == r' ] of
  53. [x] -> x
  54. _ -> 0.0
  55. renderRow :: Prob Result -> Int -> Cairo.Render ()
  56. renderRow results offset = do
  57. let f = find Failure results
  58. p = find Partial results
  59. s = find Success results
  60. o = 50.0 * fromIntegral offset + 5
  61. Cairo.rectangle 5 o (f * 290) 40
  62. Cairo.setSourceRGB 1.0 0.0 0.0
  63. Cairo.fill
  64. Cairo.rectangle (5 + f * 290) o (p * 290) 40
  65. Cairo.setSourceRGB 1.0 1.0 0.0
  66. Cairo.fill
  67. Cairo.rectangle (5 + (f + p) * 290) o (s * 290) 40
  68. Cairo.setSourceRGB 0.0 0.0 1.0
  69. Cairo.fill
  70. Cairo.setSourceRGB 0.0 0.0 0.0
  71. layout <- text ("+" ++ show offset)
  72. Cairo.moveTo 310.0 (50.0 * fromIntegral offset)
  73. Pango.showLayout layout
  74. text :: String -> Cairo.Render Pango.PangoLayout
  75. text t = do
  76. layout <- Pango.createLayout t
  77. Monad.liftIO $ do
  78. font <- Pango.fontDescriptionNew
  79. Pango.fontDescriptionSetFamily font "Fira Sans"
  80. Pango.fontDescriptionSetSize font 30
  81. Pango.layoutSetFontDescription layout (Just font)
  82. return layout
  83. main :: IO ()
  84. main = do
  85. let results = [ (n, roll n) | n <- [0..5] ]
  86. width = 400
  87. height = length results * 50 + 50
  88. surface <- Cairo.createImageSurface Cairo.FormatRGB24 width height
  89. Cairo.renderWith surface $ do
  90. Cairo.setSourceRGB 1.0 1.0 1.0
  91. Cairo.rectangle 0.0 0.0 (fromIntegral width) (fromIntegral height)
  92. Cairo.fill
  93. Monad.forM_ results $ \(n, r) ->
  94. renderRow r n
  95. Cairo.setSourceRGB 0.0 0.0 0.0
  96. layout <- text "PbtA rolls (2d6)"
  97. Cairo.moveTo 20 (fromIntegral height - 50)
  98. Pango.showLayout layout
  99. Cairo.surfaceWriteToPNG surface "output.png"