使用MonadPrompt实现重播

受Brent Yorgey冒险游戏的启发,我一直在编写一个使用MonadPrompt库的小型文本冒险游戏(la Zork)。 使用它将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不是直接递归的,你不能真正从传递到操作列表中删除操作replayIO 。 它从加载保存文件的函数中获取动作的初始列表,然后它可以查看列表中的第一个动作。

到目前为止,我唯一的解决方案就是维护GameState中的重放动作列表。 我不喜欢这个,因为这意味着我不能干净地将basicIOreplayIO分开。 我想让replayIO处理它的动作列表,然后当它将控制权传递给basicIO ,该列表完全消失。

到目前为止,我已经使用runPromptM包中的runPromptM来使用提示monad,但通过查看包,runPromptC和runRecPromptC函数看起来更强大,但我不能很好地理解它们,或者如果)他们可能对我有用。

希望我已经包含足够的细节来解释我的问题,如果有人能够让我走出困境,我会非常感激。


根据我所知,无法通过运行Prompt动作中途切换提示处理程序,因此您需要一个处理程序来处理仍然存在要重播的操作的情况,以及您在“已恢复正常发挥。

我看到解决这个问题的最佳方法是将另一个StateT转换器添加到堆栈中,以存储剩余的要执行的操作列表。 这样,重放逻辑可以与basicIO的主要游戏逻辑basicIO ,并且重播处理程序可以调用lift . basicIO lift . basicIO当没有任何动作的时候,不做任何动作或者在状态之外选择动作。

链接地址: http://www.djcxy.com/p/10073.html

上一篇: Implementing replays with MonadPrompt

下一篇: Which mobile device emulators do you use to test mobile development?