MazesOfMonad-1.0.3: src/MoresmauJP/Core/Screen.hs
-- | Console handling code
-- (c) JP Moresmau 2009
module MoresmauJP.Core.Screen where
import Control.Monad.State
import Control.Monad.Writer
import Char
import Data.List
import Data.Maybe
import MoresmauJP.Util.Random
data Screen a = Screen {actions::[Action a]}
data Action a = Action {
actionName::String,
actionDescription::String,
actionFunction::(ActionFunction a)
}
type ScreenT a b= (RandT (StateT a IO)) b
type ScreenMessage = String
type ScreenMessages = [ScreenMessage]
addScreenMessage :: (MonadWriter ScreenMessages m,Monad m)=> ScreenMessage -> m ()
addScreenMessage msg=tell [msg]
type GSWScreenT a= ScreenT (GameState a) (Widget a)
type WScreenT a=WriterT ScreenMessages (RandT (StateT (GameState a) IO)) (Widget a)
instance (Monad m) => MonadRandom (WriterT ScreenMessages (RandT m)) where
getRandomRange =lift . getRandomRange
getSplit = lift getSplit
type ActionFunction a = [String] -> WScreenT a
data GameState a = GameState {gsData::a,
screen::Maybe (Screen a)
}
data Widget a= WText String |
WList [String] |
WInput [String] (String -> WScreenT a) |
WCombo [String] [String] (String -> WScreenT a) |
WCheck [String] String Bool (Bool -> WScreenT a) |
WNothing
type ScreenState a = (Widget a,GameState a)
getShowCombo :: Show b => [String] -> [b] -> ((ComboResult b) -> WScreenT a) -> Widget a
getShowCombo= getMappedCombo show
getMappedCombo :: (b->String) -> [String] -> [b] -> ((ComboResult b) -> WScreenT a) -> Widget a
getMappedCombo myShow s objs af=
let
objWithNames=map (\x->(x,myShow x)) objs
af2=(\s2 -> do
if null s2
then
af Empty
else
do
let objChosen=listToMaybe (map fst (filter (\x->(snd x) == s2) objWithNames))
case objChosen of
Just oc->af (Exact oc)
Nothing->af (Unknown s2)
)
in (WCombo s (map snd objWithNames) af2)
getPretypedWidget :: Widget a -> [String] -> WScreenT a
getPretypedWidget wc@(WCombo _ choices af) (typed:_) = do
let chosen=filter (\(x,_)->x==typed) (zipWith (\a b -> ((show a),b)) [1..] choices)
if (null chosen)
then return wc
else af (snd $ head chosen)
getPretypedWidget w _=return w
removeWithName :: [Action a] -> [Action b] -> [Action a]
removeWithName aa ab=
let names=(map actionName aa) \\ (map actionName ab)
in filter (\a -> elem (actionName a) names) aa
data ComboResult a= Empty | Unknown String | Exact a
deriving (Show,Read)
start :: ScreenState a -> IO(a)
start gs =
do
commandLoop gs
commandLoop :: ScreenState a -> IO(a)
commandLoop (w,gs)=do
GameState s2 _ <- ioRandT (commandLoop2 w) gs
--sg<-getStdGen
--GameState s2 _ <- execStateT (evalRandT (runWriterT $ commandLoop2 w) (ProductionRandom sg)) gs
return s2
commandLoop2 :: Widget a -> GSWScreenT a
commandLoop2 w = do
--msgs <- lift $ getMessages
--listen
--when (not $ null msgs) (liftIO $ (mapM_ putStrLn (reverse msgs)))
af<- liftIO $ renderWidget w
scr <- gets screen
if (isJust scr)
then
if isJust af
then
do
(w2,msgs) <-runWriterT $ fromJust af
when (not $ null msgs) (liftIO $ (mapM_ putStrLn (reverse msgs)))
commandLoop2 w2
else
do
liftIO $ putStr ">"
input <- liftIO $ getLine
let cmds = words input
if null cmds
then
commandLoop2 WNothing
else
do
let (cmd:_)=cmds
let af2 = getAction (map Char.toLower cmd) (actions $ fromJust scr)
(w2,msgs)<-runWriterT (af2 cmds)
when (not $ null msgs) (liftIO $ (mapM_ putStrLn msgs))
commandLoop2 w2
else
return WNothing
renderWidget :: Widget a -> IO(Maybe(WScreenT a))
renderWidget (WNothing)= do
return Nothing
renderWidget (WText s)= do
putStrLn s
return Nothing
renderWidget (WList ss)=do
mapM_ putStrLn ss
return Nothing
renderWidget (WInput ss1 af)=do
mapM_ putStrLn ss1
input <- getLine
return (Just $ af input)
renderWidget (WCheck ss1 s def af)=do
mapM_ putStrLn ss1
let choices=if def then " (Y/n)" else " (y/N)"
putStrLn (s ++choices)
cmds <- getArgs
let ch=if null cmds
then def
else (map toUpper (head cmds))=="Y"
return (Just (af ch))
renderWidget (WCombo ss1 ss2 af)=do
mapM_ putStrLn ss1
let choices=zipWith (\a b -> ((show a),b)) [1..] ss2
mapM_ putStrLn (map (\(a,b) -> a ++ ": "++b) choices)
cmds <- getArgs
let chosen=if null cmds
then [("","")]
else filter (\(x,_)->x==(head cmds)) choices
if null chosen
then return (Just $ af "")
else return (Just $ af (snd $ head chosen))
getArgs :: IO([String])
getArgs = do
input <- getLine
return (words input)
help :: Bool -> ActionFunction a
help withSystem _ = do
let f (Action s1 s2 _)= (s1++": "++s2)
let sysLines= if withSystem
then (map f systemActions)
else []
gs <- get
let acts=actions $ fromJust $ screen gs
let wl=WList (sort ( sysLines ++
(map f acts)))
tell ["help1"]
tell ["help2"]
return (wl)
unknown ::ActionFunction a
unknown args = return (WText ("I do not understand the command " ++ (head args)))
quit :: ActionFunction a
quit _ = do
modify (\gs->gs{screen=Nothing})
return (WText ("Bye bye, hope you enjoyed the game!"))
choice :: [String] -> ActionFunction a
choice ss _ = return (WList ss)
backAction :: Screen a -> Action a
backAction sc=Action "back" "Go back to main screen" (back sc)
back :: Screen a -> ActionFunction a
back sc _ =do
(GameState a _) <- get
put (GameState a (Just sc))
return (WText "Back")
systemActions :: [Action a]
systemActions = [Action "help" "Provides help on available actions" (help True)
,Action "?" "Provides help on available actions" (help True)
,Action "quit" "Exit the game" quit]
getAction :: String -> [Action a] -> ActionFunction a
getAction "help" _ = help True
getAction cmd acts =
let
filt=(filter (\x->isPrefixOf cmd (map Char.toLower (actionName x))))
possible=(filt systemActions) ++ (filt acts)
l = length possible
in
if l==0 then
unknown
else if l==1 then
actionFunction $ head possible
else
choice (map (\(Action s1 _ _)->s1) possible)
{--
combineActionAfterIO :: ScreenState a -> ActionFunction a -> Event -> IO (ScreenState a)
combineActionAfterIO ss1@(w1,gs) af e = do
(w2,gs2)<-af e gs
return (combineWidget w1 w2,gs2)
combineActionBeforeIO :: ScreenState a -> ActionFunction a -> Event -> IO (ScreenState a)
combineActionBeforeIO ss1@(w1,gs) af e = do
(w2,gs2)<-af e gs
return (combineWidget w2 w1,gs2)
--}
{--combineActionAfter :: Widget a -> [String] -> ActionFunction a
combineActionAfter w1 cmds= do
w2<-af cmds
return (combineWidget w1 w2)
--}
{--
combineActionBefore :: ScreenState a -> PureActionFunction a -> [String] -> ScreenState a
combineActionBefore ss1@(w1,gs) af e =
let (w2,gs2)=af e gs
in (combineWidget w2 w1,gs2)
--}
combineMaybeWidget :: Widget a -> Maybe (Widget a) -> Widget a
combineMaybeWidget w Nothing = w
combineMaybeWidget w1 (Just w2) =combineWidget w1 w2
combineWidget :: Widget a -> Widget a -> Widget a
combineWidget WNothing a=a
combineWidget a WNothing=a
combineWidget (WText s1) (WText s2)=WList [s1,s2]
combineWidget (WText s1) (WList ss2)=WList (s1:ss2)
combineWidget (WText s1) (WInput ss1 ss2)=WInput (s1:ss1) ss2
combineWidget (WText s1) (WCheck ss1 ss2 ss3 ss4)=WCheck (s1:ss1) ss2 ss3 ss4
combineWidget (WText s1) (WCombo ss1 ss2 af)=WCombo (s1:ss1) ss2 af
combineWidget (WList ss1) (WText s2)=WList (ss1++[s2])
combineWidget (WList ss1) (WList ss2)=WList (ss1++ss2)
combineWidget (WList ss1) (WInput ss2 ss3)=WInput (ss1++ss2) ss3
combineWidget (WList s1) (WCheck ss1 ss2 ss3 ss4)=WCheck (s1++ss1) ss2 ss3 ss4
combineWidget (WList s1) (WCombo ss1 ss2 af)=WCombo (s1++ss1) ss2 af
combineWidget _ _=error "combineWidget: undefined combination"