Вывод ASCII анимации из Haskell?

Я пытаюсь получить очень быстрый и грязный анимированный дисплей некоторых данных, полученных с помощью Haskell. Самое простое, что нужно попробовать, - это искусство ASCII , другими словами, что-то вроде:

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

type Animation = [Frame]

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

как я могу лучше всего это сделать?

Я не могу понять вообще, как обеспечить минимальную паузу между кадрами, остальное просто с помощью putStrLn вместе с clearScreen С в ANSI-терминала пакет, нашли через этот ответ.

2 ответов


Ну, вот примерный набросок того, что я бы сделал:

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 - это-как следует из названия, частота кадров в герцах, т. е. кадров в секунду. The runFrames функция сначала преобразует каждый кадр в действие, которое его рисует, а затем дает каждому addDelay функция, которая проверяет время до и после выполнения действия, затем спит, пока не пройдет время кадра.

мой собственный код будет выглядеть немного иначе, потому что у меня обычно будет более сложный цикл, который будет делать другие вещи, например, опрос SDL для событий, фоновая обработка, передача данных на следующую итерацию и т. д. Но основная идея та же.

очевидно, что хорошая вещь в этом подходе заключается в том, что, будучи довольно простым, вы получаете последовательный кадр скорость, когда это возможно, с четким средством определения целевой скорости.


это основывается на anwer 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