【问题标题】:Output ascii animation from Haskell?从 Haskell 输出 ascii 动画?
【发布时间】:2011-11-19 00:48:48
【问题描述】:

我正在尝试对使用 Haskell 生成的一些数据进行非常快速而肮脏的动画显示。最简单的尝试似乎是 ASCII 艺术——换句话说,类似于:

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

type Animation = [Frame]

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

我怎样才能最好地做到这一点?

我完全想不通的部分是如何确保帧之间的停顿最少;其余部分使用putStrLnansi-terminal 包中的clearScreen 很简单,通过this answer 找到。

【问题讨论】:

  • 如果您清除屏幕,它会闪烁。 haskell 有一个 ascii 分形缩放器,检查一下。

标签: animation haskell ascii-art


【解决方案1】:

嗯,这是我要做的粗略草图:

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 以获取事件、执行后台处理、将数据传递给下一次迭代等。但基本思想是一样的。

显然,这种方法的好处在于,虽然仍然相当简单,但您可以在可能的情况下获得一致的帧速率,并通过明确的方式指定目标速度。

【讨论】:

  • 非常感谢!最后,我基本上使用了这个,但将getTicks 替换为System.Time 中的getClockTime,以避免下载额外包的工作。 (Haskell 是一种懒惰的语言,对吧?:-))
【解决方案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

【讨论】:

  • 啊,太好了!在我的大部分实际代码中,我都有基于时间差异的参数动画,因此并不太担心确切的帧速率,这就是为什么我没有想到做这种事情的原因我的头。不过,绝对是离散帧的最佳选择。
猜你喜欢
  • 2011-11-22
  • 2013-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多