module Terminal.Game.GameLoop where
import Terminal.Game.Plane
import Terminal.Game.Input
import Terminal.Game.ANSI
import Terminal.Game.Utils
import Control.Concurrent
import qualified System.IO as SI
import qualified Control.Monad as CM
import qualified System.Console.ANSI as CA
import qualified System.Console.Terminal.Size as TS
import qualified System.Clock as SC
gameLoop :: String
-> s
-> (s -> Maybe Char -> IO s)
-> (s -> Plane)
-> (s -> Bool)
-> Integer
-> IO ()
gameLoop t s lf df qf fps =
SI.hSetBuffering SI.stdout SI.NoBuffering >>
SI.hSetBuffering SI.stdin SI.NoBuffering >>
SI.hSetEcho SI.stdin False >>
CA.setTitle t >>
CA.hideCursor >>
blackScreen >>
newMVar 1 >>= \frameCounter ->
newMVar Nothing >>= \inputChar ->
forkIO (inputAction inputChar) >>
forkIO (incTimer frameCounter fps) >>
logicDraw inputChar frameCounter
s lf df qf Nothing
(initFPSCounter 20) (0,0) >>
cleanAndExit
inputAction :: MVar (Maybe Char) -> IO ()
inputAction mc =
inputCharTerminal >>= \c ->
swapMVar mc (Just c) >>
inputAction mc
incTimer :: MVar Integer -> Integer -> IO ()
incTimer mi fps = modifyMVar_ mi (return . succ) >>
threadDelay delayAmount >>
incTimer mi fps
where
delayAmount :: Int
delayAmount = fromIntegral $ div (10^6) fps
logicDraw :: MVar (Maybe Char) -> MVar Integer -> s ->
(s -> Maybe Char -> IO s) ->
(s -> Plane) ->
(s -> Bool) ->
Maybe Plane ->
FPSCounter ->
(Width, Height) ->
IO ()
logicDraw mc mi s lf df qf opln fc td =
threadDelay 300 >>
if qf s
then return ()
else
readMVar mi >>= \k ->
if k <= 0
then logicDraw mc mi s lf df qf opln fc td
else
readMVarNothing mc >>= \c ->
modifyMVar mi (\a -> return (a1, a1)) >>= \k' ->
lf s c >>= \s' ->
if k' > 0
then logicDraw mc mi s' lf df qf opln fc td
else
screenSize >>= \td'@(tw, th) ->
let resc = td /= td' in
CM.when resc blackScreen >>
let opln' | resc = Nothing
| otherwise = opln
npln = df s'
cFps = getCurrFPS fc in
blitPlane tw th opln' npln cFps >>
tickCounter fc >>= \fc' ->
logicDraw mc mi s' lf df qf (Just npln) fc' td'
data FPSCounter = FPSCounter Integer Integer SC.TimeSpec Integer
initFPSCounter :: Integer -> FPSCounter
initFPSCounter x = FPSCounter x 0 0 0
tickCounter :: FPSCounter -> IO FPSCounter
tickCounter (FPSCounter g e t1 cf)
| g > e = return (FPSCounter g (e+1) t1 cf)
| g == e = SC.getTime SC.Monotonic >>= \t2 ->
let dtn = SC.toNanoSecs $ SC.diffTimeSpec t2 t1
fr = fi dtn / fi (g+1)
fps = round $ fi (10^9) / fr in
return (FPSCounter g 0 t2 fps)
| otherwise = error "tickCounter: g < e"
where
fi = fromIntegral
getCurrFPS :: FPSCounter -> Integer
getCurrFPS (FPSCounter _ _ _ cFps) = cFps
cleanAndExit :: IO ()
cleanAndExit = CA.setSGR [CA.Reset] >>
CA.clearScreen >>
CA.setCursorPosition 0 0 >>
CA.showCursor
readMVarNothing :: MVar (Maybe a) -> IO (Maybe a)
readMVarNothing mvar = readMVar mvar >>= \ma ->
CM.unless (null ma)
(() <$ swapMVar mvar Nothing) >>
return ma
blackScreen :: IO ()
blackScreen = CA.setCursorPosition 0 0 >>
screenSize >>= \(w, h) ->
CM.replicateM_ (fromIntegral $ w*h) (putChar ' ')