Bueno, aquí hay un esbozo de lo que haría:
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
Obviamente estoy usando SDL aquí puramente para getTicks
, porque es lo que he usado antes. Siéntase libre de reemplazarlo con cualquier otra función para obtener la hora actual.
El primer argumento para runFrames
es, como su nombre indica, la velocidad de fotogramas en hercios, es decir, fotogramas por segundo. La función runFrames
primero convierte cada cuadro en una acción que lo dibuja, luego da a cada uno a la función addDelay
, que verifica el tiempo antes y después de ejecutar la acción, luego duerme hasta que el tiempo del marco haya pasado.
Mi propio código se vería un poco diferente, porque generalmente tendría un bucle más complicado que haría otras cosas, por ejemplo, sondear SDL para eventos, hacer procesamiento en segundo plano, pasar datos a la siguiente iteración, & do. Pero la idea básica es la misma.
Obviamente, lo bueno de este enfoque es que, aunque sigue siendo bastante simple, se obtiene una velocidad de fotogramas constante siempre que sea posible, con un medio claro de especificar la velocidad objetivo.
si borra la pantalla va a parpadear. hay un zoom fractal ascii para Haskell, compruébalo. –