Cóż, tutaj jest szorstki szkic co zrobię:
import Graphics.UI.SDL.Time (getTicks)
import Control.Concurrent (threadDelay)
type Frame = [[Char]]
type Animation = [Frame]
displayFrame :: Frame -> IO()
displayFrame = mapM_ putStrLn
timeAction :: IO() -> IO Integer
timeAction act = do t <- getTicks
act
t' <- getTicks
return (fromIntegral $ t' - t)
addDelay :: Integer -> IO() -> IO()
addDelay hz act = do dt <- timeAction act
let delay = calcDelay dt hz
threadDelay $ fromInteger delay
calcDelay dt hz = max (frame_usec - dt_usec) 0
where frame_usec = 1000000 `div` hz
dt_usec = dt * 1000
runFrames :: Integer -> Animation -> IO()
runFrames hz frs = mapM_ (addDelay hz . displayFrame) frs
Oczywiście używam SDL tu czysto getTicks
, bo to, co używałem wcześniej. Możesz go zastąpić dowolną inną funkcją, aby uzyskać aktualny czas.
Pierwszym argumentem dla runFrames
jest - jak sama nazwa wskazuje - częstość klatek w hercach, tj. Klatek na sekundę. Funkcja runFrames
najpierw konwertuje każdą klatkę do akcji, która ją rysuje, a następnie nadaje każdej z nich funkcję addDelay
, która sprawdza czas przed i po uruchomieniu akcji, a następnie śpi, aż upłynie czas ramki.
Mój własny kod wyglądałby nieco inaczej, ponieważ generalnie miałbym bardziej skomplikowaną pętlę, która robiłaby inne rzeczy, np. Odpytywanie SDL dla zdarzeń, przetwarzanie w tle, przekazywanie danych do następnej iteracji, & do. Ale podstawowa idea jest taka sama.
Oczywiście zaletą tego podejścia jest to, że będąc wciąż dość prostym, uzyskuje się spójną liczbę klatek na sekundę, o ile jest to możliwe, z wyraźnym sposobem określania prędkości docelowej.
jeśli wyczyścisz ekran, będzie migotać. jest ascii fraktalowy zoomer dla haskell, sprawdź to. –