Haskell 用MonadCompt实现回放
灵感来源于布伦特·约基的 , 我一直在写一个基于文本的小冒险游戏(LaZork),它使用 图书馆。使用它来分离IO后端非常简单 从控制游戏性的实际功能来看,但我现在正在尝试 有点复杂 基本上,我想启用撤销和重做作为游戏的一个功能。我的策略 因为这一直是为了保持一个拉链的游戏状态(其中包括什么是最后的 输入为)。因为我希望在重新加载时能够维护历史记录 在游戏中,保存文件只是玩家执行的所有输入的列表 可能会影响游戏状态(因此不包括检查库存, 说)。这样做的目的是通过保存中的输入快速重播最后一场比赛 加载游戏时的文件(跳过输出到终端,并从 列表),从而建立游戏状态的完整历史记录 下面是一些代码,基本上显示了我的设置(我为长度道歉,但这比实际代码要简单得多): 但是,Haskell 用MonadCompt实现回放,haskell,functional-programming,monads,Haskell,Functional Programming,Monads,灵感来源于布伦特·约基的 , 我一直在写一个基于文本的小冒险游戏(LaZork),它使用 图书馆。使用它来分离IO后端非常简单 从控制游戏性的实际功能来看,但我现在正在尝试 有点复杂 基本上,我想启用撤销和重做作为游戏的一个功能。我的策略 因为这一直是为了保持一个拉链的游戏状态(其中包括什么是最后的 输入为)。因为我希望在重新加载时能够维护历史记录 在游戏中,保存文件只是玩家执行的所有输入的列表 可能会影响游戏状态(因此不包括检查库存, 说)。这样做的目的是通过保存中的输入快速重播最后一场比赛
replayIO
的这种实现不起作用,因为replayIO
不起作用
直接递归,实际上无法从操作列表中删除操作
传递到replayIO
。它从该函数获取操作的初始列表
加载保存文件,然后它可以查看列表中的第一个操作
到目前为止,我想到的唯一解决办法是维护
在游戏状态中重播动作
。我不喜欢这个,因为这意味着我不能
将basicIO
和replayIO
干净地分开。我想让replayIO
处理它的
操作列表,然后将控制权传递给basicIO
,以使该列表
完全消失
到目前为止,我已经使用MonadCompt包中的runPromptM
来使用提示符
monad,但查看包,runPromptC和runRecPromptC
函数看起来更强大,但我不理解它们
好吧,看看它们在这里对我有多有用
希望我已经包含了足够的细节来解释我的问题,如果有人能带我走出困境,我将非常感激。从我所能告诉的,在运行
提示操作的中途,没有办法切换提示处理程序,因此,您将需要一个单独的处理程序来处理仍有操作需要重放的情况,以及恢复正常播放的情况
我认为解决这个问题的最佳方法是向堆栈中添加另一个StateT
转换器,以存储要执行的其余操作列表。这样,重放逻辑就可以与basicIO
中的主游戏逻辑分开,重放处理程序只需调用lift即可。基本操作
当没有剩余操作时,不要执行任何操作或选择状态以外的操作。谢谢!这个解决方案对我有用。我想我希望有一些奇特的类型的黑客可以在处理程序之间切换,但那个库太复杂了,我无法去探索(连续剧让我头疼)
data Action = UndoAction | RedoAction | Go Direction -- etc ...
-- Actions are what we parse user input into, there is also error handling
-- that I left out of this example
data RPGPrompt a where
Say :: String -> RPGPrompt ()
QueryUser :: String -> RPGPrompt Action
Undo :: RPGPrompt ( Prompt RPGPrompt ())
Redo :: RPGPrompt ( Prompt RPGPrompt ())
{-
... More prompts like save, quit etc. Also a prompt for the play function
to query the underlying gamestate (but not the GameZipper directly)
-}
data GameState = GameState { {- hp, location etc -} }
data GameZipper = GameZipper { past :: [GameState],
present :: GameState,
future :: [GameState]}
play :: Prompt RPGPrompt ()
play = do
a <- prompt (QueryUser "What do you want to do?")
case a of
Go dir -> {- modify gamestate to change location ... -} >> play
UndoAction -> prompt (Say "Undo!") >> join (prompt Undo)
...
parseAction :: String -> Action
...
undo :: GameZipper -> GameZipper
-- shifts the last state to the present state and the current state to the future
basicIO :: RPGPrompt a -> StateT GameZipper IO a
basicIO (Say x) = putStrLn x
basicIO (QueryUser query) = do
putStrLn query
r <- parseAction <$> getLine
case r of
UndoAction -> {- ... check if undo is possible etc -}
Go dir -> {- ... push old gamestate into past in gamezipper,
create fresh gamestate for present ... -} >> return r
...
basicIO (Undo) = modify undo >> return play
...
replayIO :: (RPGPrompt a -> StateT GameZipper IO a) ->
[Action] ->
RPGPrompt a ->
StateT GameZipper IO a
replayIO _ _ (Say _) = return () -- don't output anything
replayIO resume [] (QueryUser t) = resume (QueryUser t)
replayIO _ (action:actions) (Query _) =
case action of
... {- similar to basicIO here, but any non-gamestate-affecting
actions are no-ops (though the save file shouldn't record them
technically) -}
...