2011-09-16 9 views
6

Tôi đang cố gắng để có được một màn hình hoạt hình rất nhanh và dơ bẩn của một số dữ liệu được tạo ra bằng cách sử dụng Haskell. Điều đơn giản nhất để thử có vẻ là nghệ thuật ASCII - nói cách khác, điều gì đó dọc theo các dòng:Đầu ra ascii hoạt hình từ Haskell?

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

type Animation = [Frame] 

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

Làm cách nào để làm điều này tốt nhất?

Phần tôi không thể hiểu được là cách đảm bảo tạm dừng tối thiểu giữa các khung; phần còn lại là đơn giản bằng cách sử dụng putStrLn cùng với clearScreen từ gói ansi-terminal, được tìm thấy qua this answer.

+0

nếu bạn xóa màn hình, màn hình sẽ nhấp nháy. có một bộ phóng to ascii fractal cho haskell, kiểm tra nó. –

Trả lời

5

Vâng, đây là một phác thảo sơ bộ những gì tôi muốn làm:

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 

Rõ ràng là tôi đang sử dụng SDL đây hoàn toàn là getTicks, bởi vì đó là những gì tôi đã sử dụng trước đó. Hãy thay thế bằng bất kỳ chức năng nào khác để có được thời gian hiện tại.

Đối số đầu tiên cho runFrames là - như tên cho thấy - tốc độ khung hình trong hertz, tức là, khung hình trên giây. Chức năng runFrames trước tiên chuyển đổi mỗi khung thành một hành động mà vẽ nó, sau đó đưa mỗi chức năng addDelay, kiểm tra thời gian trước và sau khi chạy hành động, sau đó ngủ cho đến khi thời gian khung đã trôi qua.

Mã riêng của tôi trông khác một chút, vì tôi thường có một vòng lặp phức tạp hơn có thể thực hiện các công cụ khác, ví dụ, bỏ phiếu SDL cho sự kiện, xử lý nền, chuyển dữ liệu sang lần lặp tiếp theo, & c. Nhưng ý tưởng cơ bản là như nhau. Rõ ràng là điều tốt đẹp về cách tiếp cận này là, trong khi vẫn còn khá đơn giản, bạn sẽ có được tốc độ khung hình nhất quán khi có thể, với một phương tiện rõ ràng để xác định tốc độ mục tiêu.

+0

Cảm ơn rất nhiều! Cuối cùng, tôi đã sử dụng cơ bản, nhưng thay thế 'getTicks' bằng' getClockTime' từ 'System.Time', để tránh nỗ lực tải xuống các gói bổ sung. (Haskell là một ngôn ngữ lười biếng, phải không? :-)) – PLL

2

Điều này được xây dựng dựa trên tay cầm của C. A. McCann, hoạt động độc đáo nhưng không ổn định về thời gian trong thời gian dài, đặc biệt khi tốc độ khung hình không phải là một phần nguyên của tỷ lệ đánh dấu.

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, tốt đẹp! Trong hầu hết các mã thực tế của tôi, tôi đã có các hình ảnh tham số dựa trên sự khác biệt về thời gian, và do đó không lo lắng quá nhiều về tốc độ khung hình chính xác, đó là lý do tại sao tôi không nghĩ đến điều này đầu của tôi. Chắc chắn là cách để đi cho các khung rời rạc, mặc dù. –