Вывод 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