2011-09-16 35 views
6

我想要获得使用Haskell生成的一些数据的非常快速和肮脏的动画显示。最简单的尝试似乎是ASCII艺术 - 换句话说,沿线的东西:从Haskell输出ascii动画?

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

type Animation = [Frame] 

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

我该如何做到最好?

我根本找不到的部分是如何确保帧之间的最小暂停;剩下的是直接使用putStrLn加上ansi-terminal包中的clearScreen,通过this answer找到。

+0

如果你清除屏幕,它会闪烁。 haskell有一个ascii分形缩放,请检查它。 –

回答

5

嗯,这里是什么,我会做一个草图:

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 

很显然,我使用SDL这里纯粹是为了getTicks,因为这是我以前使用过。随意将其替换为任何其他功能以获取当前时间。

runFrames的第一个参数是 - 顾名思义 - 以赫兹为单位的帧速率,即每秒帧数。 runFrames函数首先将每个帧转换为一个动作,然后将其分配给addDelay函数,该函数检查运行动作之前和之后的时间,然后睡眠直至帧时间已过。

我自己的代码看起来会有点不同,因为我通常会有一个更复杂的循环来做其他的事情,比如轮询SDL事件,做后台处理,将数据传递给下一次迭代,& C。但基本的想法是一样的。

很显然,这种方法的好处在于,尽管仍然非常简单,但在可能的情况下,您可以获得一致的帧速率,并具有指定目标速度的明确方法。

+0

非常感谢!最后,我基本上使用了这个,但是用'getClockTime'替换'getTicks'来'System.Time',以避免下载额外的软件包。 (Haskell是一种懒惰的语言,对吧?:-)) – PLL

2

这建立在C.A.McCann的anwer上,它很好地工作,但从长远来看不是时间稳定的,特别是当帧率不是tick值的整数分数时。

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

啊,很好!在我的大部分实际代码中,我都有基于时间差异的参数化动画,因此并不太在意确切的帧速率,这就是为什么我没有想到做这种事情的顶部我的头。但绝对是离散帧的路。 –