diff options
-rw-r--r-- | sequence.cabal | 2 | ||||
-rw-r--r-- | sequence.nix | 4 | ||||
-rw-r--r-- | src/Sequence/Formula.hs | 59 | ||||
-rw-r--r-- | src/Sequence/Utils.hs | 17 | ||||
-rw-r--r-- | src/Sequence/Utils/Ask.hs | 27 |
5 files changed, 76 insertions, 33 deletions
diff --git a/sequence.cabal b/sequence.cabal index c7c58e4..47f1532 100644 --- a/sequence.cabal +++ b/sequence.cabal | |||
@@ -34,6 +34,6 @@ executable sequence | |||
34 | , table-layout | 34 | , table-layout |
35 | , game-probability | 35 | , game-probability |
36 | , readline | 36 | , readline |
37 | , either | 37 | , transformers |
38 | hs-source-dirs: src | 38 | hs-source-dirs: src |
39 | default-language: Haskell2010 \ No newline at end of file | 39 | default-language: Haskell2010 \ No newline at end of file |
diff --git a/sequence.nix b/sequence.nix index a272486..dfc6cb4 100644 --- a/sequence.nix +++ b/sequence.nix | |||
@@ -1,7 +1,7 @@ | |||
1 | { mkDerivation, base, bimap, case-insensitive, containers | 1 | { mkDerivation, base, bimap, case-insensitive, containers |
2 | , data-default, directory, either, filepath, game-probability, lens | 2 | , data-default, directory, either, filepath, game-probability, lens |
3 | , mtl, readline, Shellac, Shellac-haskeline, stdenv, table-layout | 3 | , mtl, readline, Shellac, Shellac-haskeline, stdenv, table-layout |
4 | , xdg-basedir | 4 | , transformers, xdg-basedir |
5 | }: | 5 | }: |
6 | mkDerivation { | 6 | mkDerivation { |
7 | pname = "sequence"; | 7 | pname = "sequence"; |
@@ -12,7 +12,7 @@ mkDerivation { | |||
12 | executableHaskellDepends = [ | 12 | executableHaskellDepends = [ |
13 | base bimap case-insensitive containers data-default directory | 13 | base bimap case-insensitive containers data-default directory |
14 | either filepath game-probability lens mtl readline Shellac | 14 | either filepath game-probability lens mtl readline Shellac |
15 | Shellac-haskeline table-layout xdg-basedir | 15 | Shellac-haskeline table-layout transformers xdg-basedir |
16 | ]; | 16 | ]; |
17 | license = stdenv.lib.licenses.mit; | 17 | license = stdenv.lib.licenses.mit; |
18 | } | 18 | } |
diff --git a/src/Sequence/Formula.hs b/src/Sequence/Formula.hs index 7a6e689..2a4541f 100644 --- a/src/Sequence/Formula.hs +++ b/src/Sequence/Formula.hs | |||
@@ -1,26 +1,37 @@ | |||
1 | {-# LANGUAGE RecordWildCards, RankNTypes, TypeSynonymInstances, FlexibleInstances #-} | 1 | {-# LANGUAGE RecordWildCards, RankNTypes, TypeSynonymInstances, FlexibleInstances, MultiParamTypeClasses, ViewPatterns, TypeFamilies #-} |
2 | 2 | ||
3 | module Sequence.Formula | 3 | module Sequence.Formula |
4 | ( FormulaM | 4 | ( FormulaM |
5 | , evalFormula | 5 | , evalFormula |
6 | , val | 6 | , val |
7 | , d, z | ||
7 | ) where | 8 | ) where |
8 | 9 | ||
9 | import Control.Lens | 10 | import Control.Lens |
11 | import Data.Data.Lens | ||
12 | import Data.Data (Data) | ||
13 | import Data.Typeable (Typeable) | ||
10 | 14 | ||
11 | import Control.Monad.Trans.Either | 15 | import Control.Monad.Except |
12 | import Control.Monad.Reader | 16 | import Control.Monad.Reader |
13 | import Numeric.Probability.Game.Event | 17 | import Numeric.Probability.Game.Event |
18 | import qualified Numeric.Probability.Game.Dice as D | ||
14 | 19 | ||
15 | import Sequence.Utils | 20 | import Sequence.Utils.Ask |
16 | 21 | ||
17 | import Text.Read (readMaybe) | 22 | import Text.Read (readMaybe) |
18 | 23 | ||
19 | type FormulaM input a = ReaderT input (EitherT (Question input) EventM) a | 24 | import Data.Bool |
25 | import Data.List | ||
26 | import Data.Maybe | ||
27 | |||
28 | |||
29 | type FormulaM input a = ReaderT input (ExceptT (Question input) EventM) a | ||
20 | 30 | ||
21 | data Question input = Question | 31 | data Question input = Question |
22 | { answer :: Lens' input (Maybe Int) | 32 | { answer :: Traversal' input (FormulaM input Int) |
23 | , prompt :: String | 33 | , prompt :: String |
34 | , keepResult :: Bool | ||
24 | } | 35 | } |
25 | 36 | ||
26 | instance Integral a => Num (FormulaM input a) where | 37 | instance Integral a => Num (FormulaM input a) where |
@@ -29,18 +40,36 @@ instance Integral a => Num (FormulaM input a) where | |||
29 | negate = fmap negate | 40 | negate = fmap negate |
30 | abs = fmap abs | 41 | abs = fmap abs |
31 | signum = fmap signum | 42 | signum = fmap signum |
32 | (*) x y = sum <$> (flip replicateM y =<< fromIntegral <$> x) | 43 | (*) x y = do n <- x |
44 | sum <$> replicateM (fromIntegral n) y | ||
33 | fromInteger = return . fromInteger | 45 | fromInteger = return . fromInteger |
34 | 46 | ||
47 | |||
35 | askQuestion :: MonadIO m => input -> (Question input) -> m input | 48 | askQuestion :: MonadIO m => input -> (Question input) -> m input |
36 | askQuestion input q@(Question{..}) = flip (set answer) input <$> askQ prompt (join . fmap readMaybe) | 49 | askQuestion input q@(Question{..}) = flip (set answer) input . maybe (throwError q) return <$> askQ prompt (join . fmap readMaybe) |
50 | |||
51 | evalFormula :: MonadIO m => input -> FormulaM input a -> m (input, a) | ||
52 | evalFormula = evalFormula' [] | ||
53 | where | ||
54 | evalFormula' finalChanges input formula = do | ||
55 | result <- liftIO . enact . runExceptT . (runReaderT ?? input) $ formula | ||
56 | case result of | ||
57 | Left q@(Question{..}) -> askQuestion input q >>= flip (evalFormula' $ bool (pure . set answer $ throwError q) mempty keepResult ++ finalChanges) formula | ||
58 | Right result -> return (foldr ($) input finalChanges, result) | ||
59 | |||
60 | val :: Integral a => Traversal' input (FormulaM input Int) -> String -> Bool -> FormulaM input Int | ||
61 | val answer prompt keepResult = preview answer >>= maybe (throwError Question{..}) id | ||
62 | |||
63 | -- viewL :: Lens' lInput sInput -> Iso' (FormulaM sInput a) (FormulaM lInput a) | ||
64 | -- viewL lTrav = iso asL asS | ||
65 | -- where | ||
66 | -- asL small = join $ lift . withExceptT (\Question{..} -> Question{ answer = lTrav . answer . viewL lTrav, .. }) . runReaderT small <$> view lTrav | ||
67 | -- asS :: FormulaM lInput a -> FormulaM sInput a | ||
68 | -- asS large = undefined | ||
37 | 69 | ||
38 | evalFormula :: MonadIO m => input -> FormulaM input a -> m a | 70 | -- val' :: Integral a => Lens' lInput sInput -> Traversal' sInput (FormulaM sInput Int) -> String -> Bool -> FormulaM lInput Int |
39 | evalFormula input formula = do | 71 | -- val' lTrav sTrav prompt keepResult = preview (lTrav . sTrav . viewL lTrav) >>= maybe (throwError Question{ answer = lTrav . sTrav . viewL lTrav, ..}) id |
40 | result <- liftIO . enact . runEitherT . (runReaderT ?? input) $ formula | ||
41 | case result of | ||
42 | Left q -> askQuestion input q >>= flip evalFormula formula | ||
43 | Right result -> return result | ||
44 | 72 | ||
45 | val :: Integral a => Lens' input (Maybe Int) -> String -> FormulaM input Int | 73 | d, z :: Integral a => Int -> FormulaM input a |
46 | val answer prompt = view answer >>= maybe (lift . left $ Question{..}) return | 74 | d n = lift . lift . fmap fromIntegral $ D.d n |
75 | z n = lift . lift . fmap fromIntegral $ D.z n | ||
diff --git a/src/Sequence/Utils.hs b/src/Sequence/Utils.hs index f0a8849..274a69d 100644 --- a/src/Sequence/Utils.hs +++ b/src/Sequence/Utils.hs | |||
@@ -2,8 +2,8 @@ | |||
2 | 2 | ||
3 | module Sequence.Utils | 3 | module Sequence.Utils |
4 | ( withArg, withFocus | 4 | ( withArg, withFocus |
5 | , askQ, askBool | ||
6 | , toName, fromName | 5 | , toName, fromName |
6 | , module Sequence.Utils.Ask | ||
7 | ) where | 7 | ) where |
8 | 8 | ||
9 | import Sequence.Types | 9 | import Sequence.Types |
@@ -33,8 +33,7 @@ import System.Console.Shell | |||
33 | import System.Console.Shell.ShellMonad | 33 | import System.Console.Shell.ShellMonad |
34 | import System.Console.Shell.Backend.Haskeline | 34 | import System.Console.Shell.Backend.Haskeline |
35 | 35 | ||
36 | import System.Console.Readline (readline) | 36 | import Sequence.Utils.Ask |
37 | |||
38 | 37 | ||
39 | class Argument a st | a -> st where | 38 | class Argument a st | a -> st where |
40 | arg :: String -> Sh st (Maybe a) | 39 | arg :: String -> Sh st (Maybe a) |
@@ -49,18 +48,6 @@ withFocus f = use gFocus >>= \focus -> case focus of | |||
49 | Nothing -> shellPutErrLn $ "Currently not focusing any entity" | 48 | Nothing -> shellPutErrLn $ "Currently not focusing any entity" |
50 | Just id -> f id | 49 | Just id -> f id |
51 | 50 | ||
52 | askBool :: MonadIO m => String -> Bool -> m Bool | ||
53 | askBool prompt initial = askQ prompt $ fromMaybe initial . join . fmap (eval . CI.mk) | ||
54 | where | ||
55 | eval "yes" = Just True | ||
56 | eval "y" = Just True | ||
57 | eval "no" = Just False | ||
58 | eval "n" = Just False | ||
59 | eval _ = Nothing | ||
60 | |||
61 | askQ :: MonadIO m => String -> (Maybe String -> a) -> m a | ||
62 | askQ prompt eval = eval <$> liftIO (readline $ prompt ++ " ") | ||
63 | |||
64 | unaligned = view faction' def | 51 | unaligned = view faction' def |
65 | 52 | ||
66 | toName :: MonadState GameState m => EntityIdentifier -> m String | 53 | toName :: MonadState GameState m => EntityIdentifier -> m String |
diff --git a/src/Sequence/Utils/Ask.hs b/src/Sequence/Utils/Ask.hs new file mode 100644 index 0000000..8020656 --- /dev/null +++ b/src/Sequence/Utils/Ask.hs | |||
@@ -0,0 +1,27 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | |||
3 | module Sequence.Utils.Ask | ||
4 | ( askQ, askBool | ||
5 | ) where | ||
6 | |||
7 | import System.Console.Readline (readline) | ||
8 | |||
9 | import Control.Monad.IO.Class | ||
10 | import Control.Monad | ||
11 | |||
12 | import Data.CaseInsensitive (CI) | ||
13 | import qualified Data.CaseInsensitive as CI | ||
14 | |||
15 | import Data.Maybe | ||
16 | |||
17 | askBool :: MonadIO m => String -> Bool -> m Bool | ||
18 | askBool prompt initial = askQ prompt $ fromMaybe initial . join . fmap (eval . CI.mk) | ||
19 | where | ||
20 | eval "yes" = Just True | ||
21 | eval "y" = Just True | ||
22 | eval "no" = Just False | ||
23 | eval "n" = Just False | ||
24 | eval _ = Nothing | ||
25 | |||
26 | askQ :: MonadIO m => String -> (Maybe String -> a) -> m a | ||
27 | askQ prompt eval = eval <$> liftIO (readline $ prompt ++ " ") | ||