Что ж, если рисовать последовательные состояния все , которые вы хотите сделать, это довольно просто. Сначала возьмите step
функцию и начальное состояние и используйте iterate
функцию . iterate step initialState
- это (бесконечный) список каждого состояния симуляции. Затем вы можете отобразить display
, чтобы получить действия ввода-вывода для рисования каждого состояния, так что вместе у вас будет что-то вроде этого:
allStates :: [SimState]
allStates = iterate step initialState
displayedStates :: [IO ()]
displayedStates = fmap display allStates
Самый простой способ запустить его - использовать функцию intersperse
, чтобы установить действие "задержки" между каждым действием отображения, затем использовать функцию sequence_
запустить все это:
main :: IO ()
main = sequence_ $ intersperse (delay 20) displayedStates
Конечно, это означает, что вы должны принудительно завершить приложение, и исключает какой-либо вид интерактивности, так что это не совсем хороший способ сделать это вообще.
Более разумным подходом было бы чередовать такие вещи, как «видеть, должно ли приложение выйти» на каждом шаге. Вы можете сделать это с явной рекурсией:
runLoop :: SimState -> IO ()
runLoop st = do display st
isDone <- checkInput
if isDone then return ()
else delay 20 >> runLoop (step st)
Мой предпочтительный подход - вместо этого писать нерекурсивные шаги, а затем использовать более абстрактный комбинатор циклов. К сожалению, в стандартных библиотеках не очень хорошая поддержка для этого, но это будет выглядеть примерно так:
runStep :: SimState -> IO SimState
runStep st = do display st
delay 20
return (step st)
runLoop :: SimState -> IO ()
runLoop initialState = iterUntilM_ checkInput runStep initialState
Реализация функции iterUntilM_
оставлена читателю как упражнение, хе.