timers-tick-0.1.0.0: src/Control/Timer/Tick.hs
--------------------------------------------------------------------------------
-- |
-- Module : Control.Timer.Tick
-- Copyright : (C) 2018 Francesco Ariis
-- License : BSD3 (see LICENSE file)
--
-- Maintainer : Francesco Ariis <fa-ml@ariis.it>
-- Stability : provisional
-- Portability : portable
--
-- Timers and timed resources (animations, etc.) utilities for tick-based
-- programs.
--
--------------------------------------------------------------------------------
module Control.Timer.Tick ( -- * Simple timers
creaTimer,
Timer,
-- * Timed Resources
TimedRes,
creaTimedRes,
Loop(..),
-- * Use
tick,
ticks,
reset,
-- * Query
isLive,
isExpired,
fetch
)
where
-----------
-- TYPES --
-----------
-- | A timed resource is a timer which, at any given moment, points to
-- a specific item (like an animation).
--
-- Example:
--
-- @
-- run = creaTimedRes (Times 1) [(2, "a "), (1, "b "), (2, "c ")]
-- main = count run
-- where
-- count t | isExpired t = putStrLn "\nOver!"
-- | otherwise = do putStr (fetch t)
-- count (tick t)
-- -- λ> main
-- -- a a b c c
-- -- Over!
-- @
data TimedRes a = TimedRes { -- init
tSteps :: [TimerStep a],
tLoop :: Loop,
tOrigLoop :: Loop,
-- convenience
tMaxTicks :: Integer,
-- curr
tCurrTick :: Integer,
tExpired :: Bool
}
deriving (Show, Eq)
type TimerStep a = (Integer, a)
-- | Number of times to repeat the animation.
data Loop = Times Integer -- currentLoop and maxLoop
| AlwaysLoop
deriving (Show, Eq)
-- todo Monoid (or semigroup) <> for timers [2.0]
------------
-- CREATE --
------------
type Timer = TimedRes ()
-- | Creates a 'Timer' expiring in @x@ ticks.
--
-- Example:
--
-- @
-- main = count (creaTimer 4)
-- where
-- count t | isExpired t = putStrLn "Over!"
-- | otherwise = do putStrLn "Ticking."
-- count (tick t)
--
-- -- λ> main
-- -- Ticking.
-- -- Ticking.
-- -- Ticking.
-- -- Ticking.
-- -- Over!
-- @
creaTimer :: Integer -> Timer
creaTimer c = creaTimedRes (Times 1) [(c, ())]
-- | Creates a time-based resource, like an animation.
creaTimedRes :: Loop -> [(Integer, a)] -> TimedRes a
creaTimedRes _ [] = error "Cannot create an empty TimedRes"
creaTimedRes l ss = TimedRes ss l l
(sum . map fst $ ss)
0 False
-------------
-- OPERATE --
-------------
-- | Ticks the timer (one step).
tick :: TimedRes a -> TimedRes a
tick t | isExpired t = t
| otherwise =
let t' = t { tCurrTick = succ (tCurrTick t) } in
if tCurrTick t' == tMaxTicks t'
then maxed t'
else t'
where
maxed :: TimedRes a -> TimedRes a
maxed tm = case tLoop tm of
Times 1 -> tm { tLoop = Times 0,
tExpired = True }
AlwaysLoop -> tm { tCurrTick = 0 }
Times n -> tm { tLoop = Times (n-1),
tCurrTick = 0 }
-- | Ticks the timer (multiple steps).
ticks :: Integer -> TimedRes a -> TimedRes a
ticks 1 t = tick t
ticks n t | n < 1 = error "negative number passed to `ticks`"
| otherwise = ticks (n-1) (tick t)
-- | Equal to @not isExpired@.
isLive :: TimedRes a -> Bool
isLive t = not $ tExpired t
-- | Checks wheter the timer is expired (an expired timer will not
-- respond to 'tick').
isExpired :: TimedRes a -> Bool
isExpired t = tExpired t
-- | Fetches the current resource of the timer.
fetch :: TimedRes a -> a
fetch t = bl !! (fromIntegral $ tCurrTick t)
where
bl = concatMap (\(c, a) -> replicate (fromIntegral c) a) $ tSteps t
-- todo having another input apart from []? maybe a function?
-- | Resets the timer to its original state.
reset :: TimedRes a -> TimedRes a
reset t = t { tCurrTick = 0,
tExpired = False,
tLoop = tOrigLoop t }
-- todo elapsed time? ticking time?