Вывести ascii анимацию из Haskell? - PullRequest
       19

Вывести ascii анимацию из Haskell?

7 голосов
/ 17 сентября 2011

Я пытаюсь получить очень быстрое и грязное анимированное отображение некоторых данных, полученных с помощью 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 ]

5 голосов
/ 17 сентября 2011

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

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 для событий, выполнение фоновой обработки, передача данных на следующую итерацию и т. Д. Но основная идея та же самая.

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

2 голосов
/ 18 сентября 2011

Это основано на ответе С. А. Макканна, который хорошо работает, но не стабилен во времени в долгосрочной перспективе, особенно когда частота кадров не является целой долей частоты тиков.

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
...