Types.hs 3.1 KB

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