Types.hs 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. {-# LANGUAGE OverloadedStrings #-}
  2. module Types where
  3. import qualified Control.Exception as Exn
  4. import qualified Data.Map.Strict as Map
  5. import qualified Data.Text as Text
  6. import qualified System.Random as Rand
  7. type TableName = Text.Text
  8. type Roll = Int
  9. data Range = Range { rFrom :: Roll, rTo :: Roll }
  10. deriving (Eq, Show)
  11. type TableMap = Map.Map TableName Table
  12. data Table = Table
  13. { tableName :: Text.Text
  14. , tableChoices :: [(Range, Result)]
  15. } deriving (Eq, Show)
  16. data Fragment
  17. = FragText Text.Text
  18. | FragRoll Text.Text
  19. deriving (Eq, Show)
  20. data Result = Result { fromResult :: [Fragment] }
  21. deriving (Eq, Show)
  22. -- * Values
  23. data Value = Value
  24. { valueMsg :: Text.Text
  25. , valueFrom :: TableName
  26. , valueResult :: Roll
  27. , valueSources :: [Value]
  28. } deriving (Eq, Show)
  29. concatValues :: Context -> [Value] -> Value
  30. concatValues _ [v] = v
  31. concatValues Context { ctxSelf = table, ctxRoll = roll } vs = Value
  32. { valueMsg = foldMap valueMsg vs
  33. , valueFrom = table
  34. , valueResult = roll
  35. , valueSources = vs
  36. }
  37. bareValue :: Context -> Text.Text -> Value
  38. bareValue Context { ctxSelf = table, ctxRoll = roll } text = Value
  39. { valueMsg = text
  40. , valueFrom = table
  41. , valueResult = roll
  42. , valueSources = []
  43. }
  44. stripValue :: Value -> Value
  45. stripValue value = value { valueMsg = Text.strip (valueMsg value) }
  46. -- * Exceptions
  47. data NoSuchTable = NoSuchTable Text.Text
  48. deriving (Eq, Show)
  49. instance Exn.Exception NoSuchTable where
  50. -- * Context
  51. data Context = Context
  52. { ctxMap :: TableMap
  53. , ctxRoll :: Int
  54. , ctxSelf :: Text.Text
  55. }
  56. -- * Evaluating Tables
  57. findTable :: Text.Text -> Context -> Maybe Table
  58. findTable name ctx = Map.lookup name (ctxMap ctx)
  59. computeFragments :: Context -> Fragment -> IO Value
  60. computeFragments ctx (FragText msg) = pure (bareValue ctx msg)
  61. computeFragments ctx (FragRoll name) =
  62. let absolute = case Text.stripPrefix "self" name of
  63. Just rest -> ctxSelf ctx <> rest
  64. Nothing -> name
  65. in case findTable absolute ctx of
  66. Just t -> rollTable (ctxMap ctx) t
  67. Nothing -> Exn.throwIO (NoSuchTable absolute)
  68. computeResult :: Context -> Result -> IO Value
  69. computeResult ctx (Result msgs) = do
  70. vs <- mapM (computeFragments ctx) msgs
  71. pure (concatValues ctx vs)
  72. tableDie :: Table -> Int
  73. tableDie t = maximum [ x | (Range _ x, _) <- tableChoices t ]
  74. rollTable :: TableMap -> Table -> IO Value
  75. rollTable tables t = do
  76. roll <- Rand.randomRIO (1, tableDie t)
  77. let ctx = Context
  78. { ctxMap = tables
  79. , ctxRoll = roll
  80. , ctxSelf = tableName t
  81. }
  82. case [ result
  83. | (range, result) <- tableChoices t
  84. , roll >= rFrom range && roll <= rTo range
  85. ] of
  86. [choice] -> stripValue <$> computeResult ctx choice
  87. _ -> error $ unwords
  88. [ "bad table "
  89. , Text.unpack (tableName t)
  90. , "(roll of"
  91. , show roll
  92. , "has no matching result)"
  93. ]