从Haskell输出ascii动画?

7

我想要用Haskell生成一些数据并制作一个快速简单的动画显示。最简单的尝试似乎是ASCII艺术-换句话说,类似于以下内容:

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

type Animation = [Frame]

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

我应该如何最好地做到这一点?

我完全想不出如何确保帧之间的最小暂停; 其余部分使用来自ansi-terminal包的putStrLnclearScreen结合,通过此答案找到。


如果你清空屏幕,它会闪烁。有一个适用于 Haskell 的 ASCII 分形缩放器,请查看一下。 - Karoly Horvath
2个回答

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事件,进行后台处理,将数据传递给下一次迭代等等。但基本思路是相同的。
显然,这种方法的好处是,在保持足够简单的同时,可以获得一致的帧速率,并且有明确的指定目标速度的方法。

1
非常感谢!最终,我基本上使用了这个,但是将 getTicks 替换为来自 System.TimegetClockTime ,以避免下载额外的包所需的努力。(Haskell 是一门惰性语言,对吧? :-) ) - PLL

2

这是在C.A. McCann的回答基础上建立的,虽然它运行良好,但从长远来看并不稳定,特别是当帧速率不是时钟频率的整数分数时。

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

啊,太好了!在我的大部分实际代码中,我都有基于时间差的参数化动画,因此并不太担心精确的帧率,这就是为什么我没有想到这种事情。但对于离散帧来说,这绝对是正确的方法。 - C. A. McCann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接