-- License: GPL v2 or later
module Main(main) where
import Graphics.UI.GLUT
import Data.IORef
import qualified Sim
import System.Random
import Control.Monad
import Data.Array.IArray
import Data.Word(Word32)
import Control.Arrow( (***) )
import System.Console.GetOpt
import qualified Display(doDisplay, initDisplay)
import ArrayUtils (arraySize)
import Clock (millisecondsNow)
data Flag = MillisecondsPerStep Int
defaultMillisecondsPerStep :: Int
defaultMillisecondsPerStep = 500
options :: [OptDescr Flag]
options = [
Option "s" ["milliseconds-per-step","ms/st"]
(ReqArg (MillisecondsPerStep . read) "TIME")
("Amount of TIME between simulation steps (default "
++ show defaultMillisecondsPerStep ++ ")")
]
doTimedStuff :: IORef (Sim.World,Word32) -> IO ()
doTimedStuff worldRef = do
ms <- millisecondsNow
modifyIORef worldRef (Sim.simulate *** const ms)
doIdleStuff :: Int -> IORef (Sim.World,Word32) -> IO ()
doIdleStuff msPerStep worldRef = doDisplayCallback msPerStep worldRef
randomRNGs :: RandomGen g => g -> [g]
randomRNGs rng = rng1 : randomRNGs rng2
where (rng1,rng2) = split rng
initialWorld :: StdGen -> Sim.World
initialWorld g =
let
bound = ((0,0),(15,15))
in Sim.World (
listArray bound $ map (\rng -> let (r,rng') = randomR (0,99) rng in
if r < 4 then Just $ Sim.Generator (toEnum r) 5
else if r < 16 then Just $ Sim.Mirror (toEnum (r `mod` 2)) (r<12) (r>=8)
else if r < 25 then Just Sim.Greenery
else if r < 27 then Just $ Sim.Storm 2 rng'
else if r < 40 then Just Sim.Mountain
else Nothing
) $ randomRNGs g
) (
[]
) (
[((6,6), Sim.Creature 4 g)]
) (
listArray bound (repeat 0)
) (
Sim.WorldHour 0
)
main :: IO ()
main = do
firstWorld <- liftM initialWorld newStdGen
worldRef <- newIORef (firstWorld,0)
initialDisplayMode $= [DoubleBuffered]
initialWindowSize $= Size 600 600
(_programName, args) <- getArgsAndInitialize
let (opts, _nonopts, optErrors) = getOpt Permute options args
when (not (null optErrors)) (putStrLn "do something smarter than printing these errors?" >> mapM_ putStrLn optErrors)
let msPerStep = case reverse [m | MillisecondsPerStep m <- opts] of
(m:_) -> m
[] -> defaultMillisecondsPerStep
(let triggerTimer = addTimerCallback msPerStep (doTimedStuff worldRef >> triggerTimer)
in triggerTimer)
--noooo! don't redisplay at 600framespersecond just because you can!
-- idleCallback $= Just (doIdleStuff msPerStep worldRef)
-- let's say we want 30 fps, way faster than anything changes anyway
-- 1000ms / 30 approx= 30
(let redisplayTimer = addTimerCallback 30 (doIdleStuff msPerStep worldRef >> redisplayTimer)
in redisplayTimer)
actionOnWindowClose $= MainLoopReturns
-- ^ it seems to help GHC emit profiling information
_window <- createWindow "simulation"; do
Display.initDisplay
displayCallback $= doDisplayCallback msPerStep worldRef
keyboardMouseCallback $= Just (clickCallback (readIORef worldRef))
mainLoop
clickCallback :: IO (Sim.World,Word32) -> Key -> KeyState -> Modifiers -> Position -> IO ()
clickCallback getWorld (MouseButton MiddleButton) Down _mods pos = do
--never mind being a useful gui-interface at the moment
(x,y) <- positionToZeroOneRange pos -- annoying. the window could theoretically have been resized by now.
(Sim.World machines _particles _creatures pollutions _hour, _) <- getWorld
let (width,height) = arraySize machines
let loc = (truncate (x * fromIntegral width), truncate (y * fromIntegral height))
putStrLn $ show loc ++ ":\n\t" ++ show (machines!loc) ++ "\n\tPollution: " ++ show (pollutions!loc)
clickCallback _ _ _ _ _ = return ()
positionToZeroOneRange :: Position -> IO (Double,Double)
positionToZeroOneRange (Position x y) = get viewport >>=
\(Position minX minY, Size sizeX sizeY) -> return
(fromIntegral (x - minX) / fromIntegral sizeX,
fromIntegral (fromIntegral sizeY - (y - minY) {-stupid GLUT difference of *origin* to OpenGL-}) / fromIntegral sizeY)
doDisplayCallback :: Int -> IORef (Sim.World,Word32) -> IO ()
doDisplayCallback msPerStep worldRef = do
ms <- millisecondsNow
(world, lastUpdateTime) <- readIORef worldRef
Display.doDisplay world msPerStep ms lastUpdateTime