2011-09-16 11 views
6

Próbuję uzyskać bardzo szybkie i brudne animowane wyświetlanie niektórych danych wyprodukowanych za pomocą Haskella. Najprostszym rozwiązaniem wydaje się być spróbować sztuki ASCII - innymi słowy, coś wzdłuż linii:Wyjście animacji ASCII od Haskella?

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

type Animation = [Frame] 

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

Jak mogę najlepiej to zrobić?

Część, której nie mogę w ogóle zrozumieć, to jak zapewnić minimalną przerwę między klatkami; reszta jest prosta, używając putStrLn wraz z clearScreen z paczki , znalezionej przez this answer.

+0

jeśli wyczyścisz ekran, będzie migotać. jest ascii fraktalowy zoomer dla haskell, sprawdź to. –

Odpowiedz

5

Cóż, tutaj jest szorstki szkic co zrobię:

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 

Oczywiście używam SDL tu czysto getTicks, bo to, co używałem wcześniej. Możesz go zastąpić dowolną inną funkcją, aby uzyskać aktualny czas.

Pierwszym argumentem dla runFrames jest - jak sama nazwa wskazuje - częstość klatek w hercach, tj. Klatek na sekundę. Funkcja runFrames najpierw konwertuje każdą klatkę do akcji, która ją rysuje, a następnie nadaje każdej z nich funkcję addDelay, która sprawdza czas przed i po uruchomieniu akcji, a następnie śpi, aż upłynie czas ramki.

Mój własny kod wyglądałby nieco inaczej, ponieważ generalnie miałbym bardziej skomplikowaną pętlę, która robiłaby inne rzeczy, np. Odpytywanie SDL dla zdarzeń, przetwarzanie w tle, przekazywanie danych do następnej iteracji, & do. Ale podstawowa idea jest taka sama.

Oczywiście zaletą tego podejścia jest to, że będąc wciąż dość prostym, uzyskuje się spójną liczbę klatek na sekundę, o ile jest to możliwe, z wyraźnym sposobem określania prędkości docelowej.

+0

Dziękuję bardzo! W końcu użyłem w zasadzie tego, ale zastępując 'getTicks' przez' getClockTime' z 'System.Time', aby uniknąć wysiłku pobierania dodatkowych pakietów. (Haskell to leniwy język, prawda? :-)) – PLL

2

To opiera się na anwerze C.A. McCanna, który działa ładnie, ale nie jest stabilny czasowo w dłuższej perspektywie, w szczególności, gdy liczba klatek na sekundę nie jest całkowitym ułamkiem liczby kleszcza.

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 
+0

Ah, miło! W większości mojego prawdziwego kodu miałem animacje parametryczne oparte na różnicach czasowych, więc nie martwiłem się zbytnio o dokładną liczbę klatek na sekundę, dlatego nie pomyślałem o zrobieniu tego rodzaju rzeczy z góry. moja głowa. Zdecydowanie jednak sposób na oddzielne ramki. –