Реализация повторов с помощью MonadPrompt

Вдохновленный приключенческой игрой Брента Йорги , я писал небольшую текстовую приключенческую игру (а-ля Zork), который использует библиотеку MonadPrompt . Было довольно просто использовать его, чтобы отделить серверную часть ввода-вывода от фактической функции, которая управляет игровым процессом, но сейчас я пытаюсь сделать что-то более сложное с этим.

По сути, я хочу включить отмену и повтор как функцию игры. Моя стратегия заключалась в том, чтобы сохранить молнию игровых состояний (включая то, что было последним вводом ). Поскольку я хочу иметь возможность сохранять историю при перезагрузке игры, файл сохранения представляет собой просто список всех выполненных игроком вводов, которые могут повлиять на состояние игры (поэтому проверка инвентаря не будет включены, скажем ). Идея состоит в том, чтобы быстро воспроизвести последнюю игру из входных данных в файле сохранения при загрузке игры (пропуская вывод в терминал и получение входных данных из списка в файле), и тем самым создать полную историю состояний игры.

Вот код, который показывает в основном мою настройку (прошу прощения за длину, но это намного упрощено по сравнению с реальным кодом):

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 . Мне это не нравится, потому что это означает, что я не могу четко разделить basicIO и replayIO . Я бы хотел, чтобы replayIO обрабатывал свой список действий , а затем, когда он передает управление basicIO , этот список полностью исчезнет.

До сих пор я использовал runPromptM из пакета MonadPrompt для использования монады Prompt , но при просмотре пакета функции runPromptC и runRecPromptC выглядят так они гораздо более могущественны, но я недостаточно хорошо их понимаю , чтобы понять, как (или если) они могут быть мне здесь полезны.

Надеюсь, я включил достаточно подробностей, чтобы объяснить мою проблему. Если кто-нибудь может вывести меня сюда из леса, я буду очень признателен.

7
задан deontologician 20 November 2011 в 20:46
поделиться