chards/Main.hs
2018-01-03 23:50:49 +00:00

153 lines
4.4 KiB
Haskell

module Main
( main
) where
import Cards
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Data.Char
import Data.Maybe
import Data.Time
import System.Console.Haskeline
inputSettings :: Settings IO
inputSettings = Settings
{ complete = noCompletion
, historyFile = Nothing
, autoAddHistory = True
}
-- The prompt functions use a MaybeT wrapper because they can fail at any time.
-- This happens when the user presses ctrl+D (EOF).
-- Simple yes/no prompt (defaults to yes)
promptYesNo :: String -> MaybeT (InputT IO) Bool
promptYesNo question = do
i <- MaybeT $ getInputLine $ question ++ " [Y/n] "
case map toLower i of
"" -> return True
"y" -> return True
"n" -> return False
_ -> do
lift $ outputStrLn $ "Incorrect input: " ++ show i
promptYesNo question
-- Wait until user pressed Enter
promptContinue :: String -> MaybeT (InputT IO) ()
promptContinue question = void $ MaybeT $ getInputLine $ question ++ "[Enter] "
-- Just span, but with monads.
spanM :: (Monad m) => (a -> m Bool) -> [a] -> m ([a], [a])
spanM _ [] = return ([], [])
spanM f l@(x:xs) = do
result <- f x
if result
then do
(with, without) <- spanM f xs
return (x:with, without)
else do
return ([], l)
{-
- Dealing with Elements/Cards.
-}
-- Generic card counting function
countCardsBy :: (Card -> Bool) -> [Element] -> Int
countCardsBy f = length . filter elmF
where elmF e = fromMaybe False (f <$> toCard e)
-- Ask all cards in the list of elements which are due.
-- When askNthCard fails, don't modify the rest of the list.
-- This bit uses two MaybeTs inside each other, so beware :P
askCountdown :: UTCTime -> [Element] -> InputT IO [Element]
askCountdown _ [] = return []
askCountdown time elms@(e:es) =
defaultTo elms $ do
result <- runMaybeT $ do
card <- MaybeT $ return $ toCard e
guard $ isDue time card
card' <- lift $ askCardWithInfo time card (countCardsBy (isDue time) es)
lift $ lift $ (fromCard card' :) <$> askCountdown time es
case result of
Nothing -> lift $ continue
Just r -> return r
where defaultTo what monad = fromMaybe what <$> runMaybeT monad
continue = (e :) <$> askCountdown time es
-- A simple right justify
rjust :: Char -> Int -> String -> String
rjust c l s = replicate (max 0 $ l - length s) c ++ s
-- These functions use a MaybeT wrapper because they can fail at any time,
-- because they use the prompt functions.
-- Print out info about a card when asking it
askCardWithInfo :: UTCTime -> Card -> Int -> MaybeT (InputT IO) Card
askCardWithInfo time card left = do
let t = rjust ' ' 9 $ tierName $ tier card
l = rjust ' ' 3 $ show left
lift $ outputStrLn ""
lift $ outputStrLn $ "-----< tier: " ++ t ++ ", left: " ++ l ++ " >-----"
askCard time card
-- Ask the sides on a card and reset or update the card accordingly
-- Doesn't check whether the card is due or not.
askCard :: UTCTime -> Card -> MaybeT (InputT IO) Card
askCard time card = do
(_, unasked) <- spanM askSide $ sides card
mapM_ showSide $ drop 1 unasked
if null unasked
then lift $ lift $ update time card
else return $ reset time card
askSide :: String -> MaybeT (InputT IO) Bool
askSide side = do
lift $ displaySide side
promptYesNo "Did you know that side?"
showSide :: String -> MaybeT (InputT IO) ()
showSide side = do
lift $ displaySide side
promptContinue "Continue"
displaySide :: String -> InputT IO ()
displaySide side = lift (putStrLn side)
{-
- User prompt.
-}
learn :: [Element] -> InputT IO [Element]
learn elms = do
time <- lift $ getCurrentTime
askCountdown time elms
stats :: [Element] -> InputT IO ()
stats = undefined -- TODO: Use tierName
trim :: Char -> String -> String
trim c = dropWhile (== c) . reverse . dropWhile (== c) . reverse
run :: [Element] -> InputT IO [Element]
run elms = do
cmd <- getInputLine "%> "
case trim ' ' . map toLower <$> cmd of
Nothing -> return elms
Just "" -> run elms
Just "quit" -> return elms
Just "q" -> return elms
Just "learn" -> learn elms >>= run
Just "l" -> learn elms >>= run
Just "show" -> stats elms >> run elms
Just "s" -> stats elms >> run elms
Just x -> do
outputStrLn $ "Unknown command " ++ show x ++ "."
run elms
-- Maybe save cards?
main :: IO ()
main = do
elms <- runInputT inputSettings $ run testElements
mapM_ (putStrLn . show) elms