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