受到布伦特·尤里尼的启发 adventure game, 我一直在写一个小型的基于文本的冒险游戏(la Zork),它使用了 MonadPrompt 图书馆。使用它来分离IO后端非常简单 来自管理游戏玩法的实际功能,但我现在正在尝试做 事情有点复杂。
基本上,我想启用撤消和重做作为游戏的一项功能。我的策略 对于这一直是保持游戏状态的拉链(其中包括最后一个 输入是)。因为我希望能够在重新加载时保持历史记录 游戏中,保存文件只是玩家执行的所有输入的列表 可以影响游戏状态(因此不会包括检查库存, 说)。这个想法是从保存中的输入快速重放最后一个游戏 加载游戏时的文件(跳过输出到终端,并从中获取输入) 文件中的列表),从而构建了游戏状态的完整历史。
这里有一些代码基本上显示了我的设置(我为长度道歉,但这比实际代码简化了):
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功能。它需要一个后端函数来执行它 完成重播(通常是basicIO)和重播动作列表
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) -}
...
replayIO
的此实现不起作用,因为replayIO
不是
直接递归,您实际上无法从操作列表中删除Actions
传递给replayIO
。它从函数中获取最初的动作列表
加载保存文件,然后它可以查看列表中的第一个操作。
到目前为止,我所遇到的唯一解决方案是维护列表
GameState
内的重播操作。我不喜欢这个,因为这意味着我不能
彻底分开basicIO
和replayIO
。我希望replayIO
能够处理它
动作列表,然后将控制权传递给basicIO
以获取该列表
完全消失。
到目前为止,我已使用MonadPrompt包中的runPromptM
来使用提示
monad,但查看包,runPromptC和runRecPromptC
函数看起来更强大,但我不理解它们
足以看出他们在这里如何(或者如果)对我有用。
希望我已经包含足够的细节来解释我的问题,如果有人能带领我走出困境,我真的很感激。
答案 0 :(得分:3)
据我所知,在运行Prompt
操作的过程中无法切换提示处理程序,因此您需要一个处理器,可以处理仍有动作重放的情况,以及你恢复正常比赛的情况。
我看到解决此问题的最佳方法是在堆栈中添加另一个StateT
转换器,以存储要执行的剩余操作列表。这样,重放逻辑可以与basicIO
中的主游戏逻辑分开,并且当没有任何操作时,您的重播处理程序可以只调用lift . basicIO
,并执行无操作或选择操作否则就是国家。