Я думаю, что схема использования iterate
, чтобы сделать якобы чистый список операций ввода-вывода, является источником проблемы здесь. Ваш план состоит в том, чтобы обновить состояние по пользовательскому вводу, но рассмотреть последовательность состояний как поток, который вы можете «обрабатывать как список». Если я использую подлинный iterateM
для создания правильных потоков вещей, тогда все идет так, как вы хотели, чтобы они пошли. Так что, если я добавляю импорт
import Streaming -- cabal install streaming
import qualified Streaming.Prelude as S
и после ваших основных определений написать что-то вроде
runGameInfiniteStream gs = S.print $ S.take 1 $ S.dropWhile (not . gameEnded) steps
where
steps :: Stream (Of GameState) IO()
steps = S.iterateM updateGameState (return gs)
main :: IO()
main = runGameInfiniteStream (newGameState "car")
тогда я получаю
>>> main
You have 5 lifes. The word is "___"
Guess a letter:
c
You have 5 lifes. The word is "c__"
Guess a letter:
a
You have 5 lifes. The word is "ca_"
Guess a letter:
r
GameState {secretWord = "car", lives = 5, guesses = "rac"}
Я думаю, что это именно та программа, которую вы предназначены, но с использованием правильная концепция потока, а не смешивание IO и списков сложным способом. Нечто подобное можно было бы сделать с pipes
и conduit
и аналогичными пакетами.
(Добавлено позже :)
Для потоковой передачи в состояния, соответствующих чистый список символьного (эмуляции результата, вводимый пользователя), вы можете просто использовать scan
pureSteps
:: (Monad m) => GameState -> [Char] -> Stream (Of GameState) m()
pureSteps gs chars = S.scan updateState gs id (S.each chars)
это в основном такой же, как Prelude.scanl
, который также может использоваться (в чистом виде) для просмотра обновлений:
>>> S.print $ pureSteps (newGameState "hi") "hxi"
GameState {secretWord = "hi", lives = 5, guesses = ""}
GameState {secretWord = "hi", lives = 5, guesses = "h"}
GameState {secretWord = "hi", lives = 4, guesses = "h"}
GameState {secretWord = "hi", lives = 4, guesses = "ih"}
>>> mapM_ print $ scanl updateState (newGameState "hi") "hxi"
GameState {secretWord = "hi", lives = 5, guesses = ""}
GameState {secretWord = "hi", lives = 5, guesses = "h"}
GameState {secretWord = "hi", lives = 4, guesses = "h"}
GameState {secretWord = "hi", lives = 4, guesses = "ih"}
Чтобы просмотреть окончательное «выигрывающее» состояние, если оно существует, вы можете написать, например.
runPureInfinite
:: Monad m => GameState -> [Char] -> m (Of [GameState]())
runPureInfinite gs = S.toList . S.take 1 . S.dropWhile (not . gameEnded) . pureSteps gs
-- >>> S.print $ runPureInfinite (newGameState "car") "caxyzr"
-- [GameState {secretWord = "car", lives = 2, guesses = "rac"}] :>()
и так далее.
«Я не в состоянии это сделать» - это не вопрос. Вы включили одну строку кода, но даже сообщение об ошибке, которое вы получаете при запуске этого гипотетического кода. Если вы попробовали это, и это дало ошибку, почему бы не включить ошибку, чтобы спасти людей некоторое время? Вы должны изолировать свою проблему до кода, который достаточно мал, чтобы задать ваш вопрос. – user2407038
Хорошо, я играл с ним и пошел дальше и редактировал вопрос. – user1698641
Ну, теперь проблема очевидна - IO не ленится. Использование IO, как и у вас, безусловно, является антипаттерном. Если вы хотите, чтобы это работало с IO, вам придется использовать 'unsafeInterleaveIO', что, очевидно, ужасно. Лучше, чтобы вы удалили логику своей игры из IO, тогда желаемая семантика (т. Е. 'TakeWhile p someInfiniteList' и варианты) будет работать, как ожидалось. Вместо того, чтобы иметь 'updateGameState :: GameState -> IO GameState', вы должны иметь' updateGameState :: UserInput -> GameState -> GameState', а затем 'readUserInput :: IO [UserInput]' тривиально - просто читать карту. линии <$> getContents'. – user2407038