2011-09-16 13 views
6

Sto cercando di ottenere una visualizzazione animata molto veloce e sporca di alcuni dati prodotti usando Haskell. La cosa più semplice da provare sembra essere arte ASCII - in altre parole, qualcosa sulla falsariga di:Animazione ASCII di output da Haskell?

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

type Animation = [Frame] 

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

Come posso fare questo meglio?

La parte che non riesco a capire è come garantire una pausa minima tra i frame; il resto è immediato utilizzando putStrLn insieme a clearScreen dal pacchetto ansi-terminal, trovato tramite this answer.

+0

se si cancella lo schermo si sta per essere sfarfallio. c'è uno zoomer con frattale ascii per haskell, controllalo. –

risposta

5

Bene, ecco un abbozzo di quello che farei:

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 

Ovviamente sto usando SDL qui per puro getTicks, perché è quello che ho usato prima. Sentiti libero di sostituirlo con qualsiasi altra funzione per ottenere l'ora corrente.

Il primo argomento su runFrames è, come suggerisce il nome, la frequenza fotogrammi in hertz, ovvero fotogrammi al secondo. La funzione runFrames converte prima ciascun fotogramma in un'azione che lo disegna, quindi assegna ciascuno alla funzione addDelay, che controlla l'ora prima e dopo l'esecuzione dell'azione, quindi si addormenta fino a quando non è trascorsa l'ora del fotogramma.

Il mio codice sarebbe un po 'diverso da questo, perché in genere avrei un ciclo più complicato che farebbe altro, ad esempio, il polling SDL per eventi, l'elaborazione in background, il passaggio dei dati alla successiva iterazione, & c. Ma l'idea di base è la stessa.

Ovviamente la cosa bella di questo approccio è che, pur essendo abbastanza semplice, si ottiene un frame rate costante quando possibile, con un chiaro mezzo per specificare la velocità target.

+0

Grazie mille! Alla fine, ho usato essenzialmente questo, ma sostituendo 'getTicks' di' getClockTime' da 'System.Time', per evitare lo sforzo di scaricare pacchetti extra. (Haskell è un linguaggio pigro, giusto? :-)) – PLL

2

Questo fa riferimento all'anwer di C. A. McCann, che funziona bene ma non è stabile nel tempo a lungo termine, in particolare quando il framerate non è una frazione intera della frequenza di tick.

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, bello! Nella maggior parte del mio codice reale ho avuto animazioni parametriche basate sulle differenze di orario, e quindi non mi sono preoccupato troppo del frame rate esatto, motivo per cui non pensavo di fare questo tipo di cose del top la mia testa. Sicuramente la strada da percorrere per i frame discreti, però. –