将打印文本的函数作为参数传递,在 Haskell 中的新状态后执行它
Passing as parameter a function that prints a text, executing it after new state in Haskell
[EDIT3:包含几乎完整代码的单个文件:
https://github.com/agutie58/landOfLispInHaskell/blob/main/exampleLoLTextGameHaskell.hs
EDIT2:实际游戏的例子。]
[原题]
我有一个基于 Domain
的函数 run
和一个事件列表:
- 如果处理所有事件,它会监听新事件并且
- 如果它们是事件,它会处理其中的几个。
大多数事件修改 Domain
:
data Domain = Domain (String, World) deriving (Show)
data World = World {loc :: String, descSites :: [(Key,String)], mapSites :: [(Key, [Lloc])], objects:: [Key], siteObjects::[(Key,String)]} deriving (Show)
run :: Domain -> [Event] -> IO ()
run dm [] = do
events <- uiUpdate dm
run dm events
run _ (EventExit:_) =
return ()
run dm (e:es) =
run (dmUpdate dm e) es
我想关注的部分是run (dmUpdate dm e) es
,其中dmUpdate dm e
return是一个Domain
值:
此函数 dmUpdate
运行良好的一个示例是:
dmUpdate :: Domain -> Event -> Domain
dmUpdate (Domain v) (EventLook) = look (snd v)
dmUpdate (Domain v) (EventWalk direction) = walk direction (snd v)
dmUpdate dm _ _ = dm
其中:
look :: World -> Domain
walk :: String -> World -> Domain
-- etc.
我想呈现(打印到控制台)新状态的结果。例如:
dmUpdate (Domain v) (EventLook) = do let newDomain = look (snd v)
putStr (fst newDomain)
newDomain
但它不起作用。我尝试计算一个新的世界状态,然后做 I/O 然后尝试 return newDomain
作为参数。
我想像这样传递一个函数:
run dm (e:es) =
run (dmUpdate dm e renderMsg) es
where renderMsg txt = (putStr txt) >> (hFlush stdout)
...为了做类似的事情:
-- dmUpdate :: dmUpdate :: Domain -> Event -> (String -> IO ()) -> IO () -> Domain
dmUpdate (Domain v) (EventLook) (renderMsg) = let newDomain = look (snd v)
renderMsg (fst newDomain)
但是不起作用。
有什么想法!?提前致谢!
[EDIT1]
我也试过了:
dmUpdate :: Domain -> Event -> IO Domain
dmUpdate (Domain v) (EventLook) = do let newDomain = look (snd v)
putStr (fst newDomain)
newDomain
dmUpdate dm _ _ = () dm
...但我收到了这条消息:
[2 of 2] Compiling Main ( textGameMain.hs, interpreted )
textGameMain.hs:25:1: error:
Equations for ‘dmUpdate’ have different numbers of arguments
textGameMain.hs:(25,1)-(27,47)
textGameMain.hs:33:1-23
|
25 | dmUpdate (Domain v) (EventLook) = do let newDomain = look (snd v)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
textGameMain.hs:70:8: error:
• Couldn't match expected type ‘Domain’
with actual type ‘IO Domain’
• In the first argument of ‘run’, namely ‘(dmUpdate dm e)’
In the expression: run (dmUpdate dm e) es
In an equation for ‘run’: run dm (e : es) = run (dmUpdate dm e) es
|
70 | run (dmUpdate dm e ) es
于是,我尝试改变run
:
run dm (e:es) =
run () (dmUpdate dm e ) es
但我没能成功...:S
[EDIT2]
所以,我的代码是这样做的(它基于 Land of Lisp,并且是对 Haskell 的改编):
*主要>主要
世界>看
你在客厅里。一个巫师在沙发上大声打鼾。
从这里向西有门。
有梯子从这里上楼。
你看到地板上有威士忌。
你看到地板上有一个水桶。
世界>向西走
世界>看
你在一个美丽的花园里。你面前有一口井。
从这里向东有门。
你看到地板上有一只青蛙。
你看到地板上有一条链子。
[EDIT3]
(完整代码 - 事实上,为了简单起见,我将两个文件折叠成一个文件......可以通过其他方式添加文件吗!?)
https://github.com/agutie58/landOfLispInHaskell/blob/main/exampleLoLTextGameHaskell.hs
ghci exampleLoLTextGameHaskell.hs
main
(...然后,EDIT2...)
您的代码远未完成,包含很多 - 对于您的问题 - 不必要的部分。我复制了你的代码,简化了一点,并做了一些猜测。这是我的“固定”版本。
module Tmp where
import Text.Read (readMaybe)
{-| Be clear about your types. Introducing type alisases helps others read your code.
I simply guessed that the string-part of the Domain is the UI state -}
type UIState = String
data Domain = Domain (UIState,World) deriving Show
data World = World {loc:: Int} deriving Show
-- | I Have only implemented two directions, so that this example is easy to work with.
data Dir = L | R deriving (Read, Show)
{- | These were the event types that you had in your code. I elaborated them a little.
By using the "deriving (Read)" we get a low-code input mechanism, but you should probalbly write
your own input parser
-}
data Event = EventExit | EventWalk Dir| EventLook deriving (Read)
-- | The run-loop now has TWO steps. Render UI and get new events. Process events. Finally recurse.
run :: Domain -> [Event] -> IO ()
run dm [] = uiUpdate dm >> getAction >>= run dm
run _ (EventExit:_) = return ()
run dm (e:es) = run (dmUpdate dm e) es
{-| Update the domain when a single event acts on it -}
dmUpdate :: Domain -> Event -> Domain
dmUpdate (Domain v) (EventLook) = look (snd v)
dmUpdate (Domain v) (EventWalk direction) = walk direction (snd v)
dmUpdate dm _ = dm
look w = Domain ("You are at coordinate " ++ (show .loc $ w), w)
walk L w@World{loc=l}= Domain ("You went left", w{loc=l-1})
walk R w@World{loc=l} = Domain ("You went right", w{loc=l+1})
{-| Present the "output" that the domain holds for us -}
uiUpdate :: Domain -> IO ()
uiUpdate dm = do
let Domain (usState,s) = dm
putStrLn usState
{-| Ask user for input. Only a single event is collected. -}
getAction :: IO [Event]
getAction = do
putStrLn "What do you want to do? Choose between EventExit | EventWalk R | EventWalk L | EventLook"
act <- readMaybe <$> getLine
case act of
Nothing -> putStrLn "Not a valid action" >> getAction
Just evt -> pure [evt]
main :: IO ()
main = run (Domain ("",World 0)) [EventLook]
最后,您可能想查看 StateT,这样您就可以抽象出一直传递的域对象。但我想这超出了这个问题的范围。
[EDIT3:包含几乎完整代码的单个文件:
https://github.com/agutie58/landOfLispInHaskell/blob/main/exampleLoLTextGameHaskell.hs
EDIT2:实际游戏的例子。]
[原题]
我有一个基于 Domain
的函数 run
和一个事件列表:
- 如果处理所有事件,它会监听新事件并且
- 如果它们是事件,它会处理其中的几个。
大多数事件修改 Domain
:
data Domain = Domain (String, World) deriving (Show)
data World = World {loc :: String, descSites :: [(Key,String)], mapSites :: [(Key, [Lloc])], objects:: [Key], siteObjects::[(Key,String)]} deriving (Show)
run :: Domain -> [Event] -> IO ()
run dm [] = do
events <- uiUpdate dm
run dm events
run _ (EventExit:_) =
return ()
run dm (e:es) =
run (dmUpdate dm e) es
我想关注的部分是run (dmUpdate dm e) es
,其中dmUpdate dm e
return是一个Domain
值:
此函数 dmUpdate
运行良好的一个示例是:
dmUpdate :: Domain -> Event -> Domain
dmUpdate (Domain v) (EventLook) = look (snd v)
dmUpdate (Domain v) (EventWalk direction) = walk direction (snd v)
dmUpdate dm _ _ = dm
其中:
look :: World -> Domain
walk :: String -> World -> Domain
-- etc.
我想呈现(打印到控制台)新状态的结果。例如:
dmUpdate (Domain v) (EventLook) = do let newDomain = look (snd v)
putStr (fst newDomain)
newDomain
但它不起作用。我尝试计算一个新的世界状态,然后做 I/O 然后尝试 return newDomain
作为参数。
我想像这样传递一个函数:
run dm (e:es) =
run (dmUpdate dm e renderMsg) es
where renderMsg txt = (putStr txt) >> (hFlush stdout)
...为了做类似的事情:
-- dmUpdate :: dmUpdate :: Domain -> Event -> (String -> IO ()) -> IO () -> Domain
dmUpdate (Domain v) (EventLook) (renderMsg) = let newDomain = look (snd v)
renderMsg (fst newDomain)
但是不起作用。
有什么想法!?提前致谢!
[EDIT1]
我也试过了:
dmUpdate :: Domain -> Event -> IO Domain
dmUpdate (Domain v) (EventLook) = do let newDomain = look (snd v)
putStr (fst newDomain)
newDomain
dmUpdate dm _ _ = () dm
...但我收到了这条消息:
[2 of 2] Compiling Main ( textGameMain.hs, interpreted )
textGameMain.hs:25:1: error:
Equations for ‘dmUpdate’ have different numbers of arguments
textGameMain.hs:(25,1)-(27,47)
textGameMain.hs:33:1-23
|
25 | dmUpdate (Domain v) (EventLook) = do let newDomain = look (snd v)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
textGameMain.hs:70:8: error:
• Couldn't match expected type ‘Domain’
with actual type ‘IO Domain’
• In the first argument of ‘run’, namely ‘(dmUpdate dm e)’
In the expression: run (dmUpdate dm e) es
In an equation for ‘run’: run dm (e : es) = run (dmUpdate dm e) es
|
70 | run (dmUpdate dm e ) es
于是,我尝试改变run
:
run dm (e:es) =
run () (dmUpdate dm e ) es
但我没能成功...:S
[EDIT2]
所以,我的代码是这样做的(它基于 Land of Lisp,并且是对 Haskell 的改编):
*主要>主要 世界>看 你在客厅里。一个巫师在沙发上大声打鼾。 从这里向西有门。 有梯子从这里上楼。 你看到地板上有威士忌。 你看到地板上有一个水桶。 世界>向西走 世界>看 你在一个美丽的花园里。你面前有一口井。 从这里向东有门。 你看到地板上有一只青蛙。 你看到地板上有一条链子。
[EDIT3]
(完整代码 - 事实上,为了简单起见,我将两个文件折叠成一个文件......可以通过其他方式添加文件吗!?)
https://github.com/agutie58/landOfLispInHaskell/blob/main/exampleLoLTextGameHaskell.hs
ghci exampleLoLTextGameHaskell.hs
main
(...然后,EDIT2...)
您的代码远未完成,包含很多 - 对于您的问题 - 不必要的部分。我复制了你的代码,简化了一点,并做了一些猜测。这是我的“固定”版本。
module Tmp where
import Text.Read (readMaybe)
{-| Be clear about your types. Introducing type alisases helps others read your code.
I simply guessed that the string-part of the Domain is the UI state -}
type UIState = String
data Domain = Domain (UIState,World) deriving Show
data World = World {loc:: Int} deriving Show
-- | I Have only implemented two directions, so that this example is easy to work with.
data Dir = L | R deriving (Read, Show)
{- | These were the event types that you had in your code. I elaborated them a little.
By using the "deriving (Read)" we get a low-code input mechanism, but you should probalbly write
your own input parser
-}
data Event = EventExit | EventWalk Dir| EventLook deriving (Read)
-- | The run-loop now has TWO steps. Render UI and get new events. Process events. Finally recurse.
run :: Domain -> [Event] -> IO ()
run dm [] = uiUpdate dm >> getAction >>= run dm
run _ (EventExit:_) = return ()
run dm (e:es) = run (dmUpdate dm e) es
{-| Update the domain when a single event acts on it -}
dmUpdate :: Domain -> Event -> Domain
dmUpdate (Domain v) (EventLook) = look (snd v)
dmUpdate (Domain v) (EventWalk direction) = walk direction (snd v)
dmUpdate dm _ = dm
look w = Domain ("You are at coordinate " ++ (show .loc $ w), w)
walk L w@World{loc=l}= Domain ("You went left", w{loc=l-1})
walk R w@World{loc=l} = Domain ("You went right", w{loc=l+1})
{-| Present the "output" that the domain holds for us -}
uiUpdate :: Domain -> IO ()
uiUpdate dm = do
let Domain (usState,s) = dm
putStrLn usState
{-| Ask user for input. Only a single event is collected. -}
getAction :: IO [Event]
getAction = do
putStrLn "What do you want to do? Choose between EventExit | EventWalk R | EventWalk L | EventLook"
act <- readMaybe <$> getLine
case act of
Nothing -> putStrLn "Not a valid action" >> getAction
Just evt -> pure [evt]
main :: IO ()
main = run (Domain ("",World 0)) [EventLook]
最后,您可能想查看 StateT,这样您就可以抽象出一直传递的域对象。但我想这超出了这个问题的范围。