2011-09-16 11 views
6

Estoy tratando de obtener una visualización animada muy rápida y sucia de algunos datos producidos con Haskell. Lo más sencillo para tratar parece ser el arte ASCII - en otras palabras, algo a lo largo de las líneas de:¿Salida de animación ascii de Haskell?

type Frame = [[Char]]  -- each frame is given as an array of characters 

type Animation = [Frame] 

display :: Animation -> IO() 
display = ?? 

¿Cómo puedo hacer esto mejor?

La parte que no puedo entender es cómo asegurar una pausa mínima entre fotogramas; el resto es sencillo usando putStrLn junto con clearScreen del paquete ansi-terminal, encontrado a través de this answer.

+0

si borra la pantalla va a parpadear. hay un zoom fractal ascii para Haskell, compruébalo. –

Respuesta

5

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.

+0

¡Muchas gracias! Al final, usé esencialmente esto, pero reemplazando 'getTicks' por' getClockTime' de 'System.Time', para evitar el esfuerzo de descargar paquetes adicionales. (Haskell es un lenguaje perezoso, ¿verdad? :-)) – PLL

2

Esto se basa en el generador de C.A. McCann, que funciona bien pero no es estable en el tiempo a largo plazo, en particular cuando la velocidad de fotogramas no es una fracción entera de la tasa de tics.

import GHC.Word (Word32) 

-- import CAMcCann'sAnswer (Frame, Animation, displayFrame, getTicks, threadDelay) 

atTick :: IO() -> Word32 -> IO() 
act `atTick` t = do 
    t' <- getTicks 
    let delay = max (1000 * (t-t')) 0 
    threadDelay $ fromIntegral delay 
    act 

runFrames :: Integer -> Animation -> IO() 
runFrames fRate frs = do 
    t0 <- getTicks 
    mapM_ (\(t,f) -> displayFrame f `atTick` t) $ timecode fRate32 t0 frs 
    where timecode ν t0 = zip [ t0 + (1000 * i) `div` ν | i <- [0..] ] 
     fRate32 = fromIntegral fRate :: Word32 
+0

¡Ah, bien! En la mayoría de mi código real, he tenido animaciones paramétricas basadas en las diferencias de tiempo, y por lo tanto no me preocupé demasiado por la velocidad de fotogramas exacta, razón por la cual no pensé en hacer este tipo de cosas de la parte superior de mi cabeza. Definitivamente el camino a seguir para obtener marcos discretos, sin embargo. –

Cuestiones relacionadas