Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Output ascii animation from Haskell?

I’m trying to get a very quick and dirty animated display of some data produced using Haskell. The simplest thing to try seems to be ASCII art — in other words, something along the lines of:

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

type Animation = [Frame]

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

How can I best do this?

The part I can’t figure out at all is how to ensure a minimal pause between frames; the rest is straightforward using putStrLn together with clearScreen from the ansi-terminal package, found via this answer.

like image 731
PLL Avatar asked Sep 16 '11 22:09

PLL


1 Answers

Well, here's a rough sketch of what I'd do:

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

Obviously I'm using SDL here purely for getTicks, because it's what I've used before. Feel free to replace it with any other function to get the current time.

The first argument to runFrames is--as the name suggests--the frame rate in hertz, i.e., frames per second. The runFrames function first converts each frame into an action that draws it, then gives each to the addDelay function, which checks the time before and after running the action, then sleeps until the frame time has passed.

My own code would look a bit different than this, because I'd generally have a more complicated loop that would do other stuff, e.g., polling SDL for events, doing background processing, passing data to the next iteration, &c. But the basic idea is the same.

Obviously the nice thing about this approach is that, while still being fairly simple, you get a consistent frame rate when possible, with a clear means of specifying the target speed.

like image 114
C. A. McCann Avatar answered Oct 11 '22 07:10

C. A. McCann