Вдохновленный приключенческой игрой Брента Йорги , я писал небольшую текстовую приключенческую игру (а-ля 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
выглядят так они гораздо более могущественны, но я недостаточно хорошо их понимаю
, чтобы понять, как (или если) они могут быть мне здесь полезны.
Надеюсь, я включил достаточно подробностей, чтобы объяснить мою проблему. Если кто-нибудь может вывести меня сюда из леса, я буду очень признателен.