content: 
  module Main where
  
  import Graphics.Gloss
  import Graphics.Gloss.Interface.Environment
  import System.Random
  
  -- x, y, dx, dy
  type Particle
          = (Float, Float, Float, Float)
  
  
  main :: IO ()
  main
   = do   g <- getStdGen
          (width,height) <- getScreenSize
          let initialstate = generateParticles g width height
          simulate window background fps initialstate render update
   where
          window          = FullScreen
          background      = black
          fps             = 60
          render xs       =  pictures $ map particleImage xs
          update _        = updateParticles
  
  
  -- | Generates particles from StdGen
  generateParticles :: StdGen -> Int -> Int -> [Particle]
  generateParticles gen widthInt heightInt
   = map (g . f)  tups
   where
          -- change range [0,1] ->  [-s/2,s/2]
          f = \(x,y) -> (x * width - width / 2, y * height - height / 2)
  
          -- add speed of 0
          g = \(x,y) -> (x,y,0,0)
  
          -- 200 Random float tuples
          tups            = take 50 $ zip randoms1 randoms2
          randoms1        = randoms gen1 :: [Float]
          randoms2        = randoms gen2 :: [Float]
          (gen1,gen2)     = split gen
          width           = toEnum widthInt
          height          = toEnum heightInt
  
  
  -- | Particle to its picture
  particleImage :: Particle -> Picture
  particleImage (x,y,_,_)
   = translate x y $ color white $ circleSolid 2
  
  
  -- | To update particles for next frame
  updateParticles :: Float -> [Particle] -> [Particle]
  updateParticles dt
   = (accelerateParticles dt) . (moveParticles dt)
  
  
  -- | Moves particles based on their speed
  moveParticles :: Float -> [Particle] -> [Particle]
  moveParticles dt
   = map (\(x,y,dx,dy) -> (x+dx*dt,y+dy*dt,dx,dy))
  
  
  -- | Accelerates particles based on gravity
  accelerateParticles :: Float -> [Particle] -> [Particle]
  accelerateParticles dt ps
   = map (gravitate ps dt) ps
  
  
  -- | Given particles to be gravitating to and for how long,
  --  updates a single particle's speed
  gravitate :: [Particle] -> Float -> Particle -> Particle
  gravitate [] _ p = p
  gravitate ((x',y',_,_):ps) dt p@(x,y,dx,dy)
   =  -- To dodge divByZero or near divByZero anomalies
      if separated x x' && separated y y'
      then gravitate ps dt p'
      else gravitate ps dt p
          where
            p'    = (x,y,dx+ddx,dy+ddy)
            ddx   = dirx * g
            ddy   = diry * g
            (dirx,diry) = direction (x,y) (x',y')
            g     = gravitation (x,y) (x',y')
  
  
  -- | Normalized vector from one point to another.
  direction :: (Float, Float) -> (Float, Float) -> (Float, Float)
  direction (x,y) (x',y')
   = (dx * scale', dy * scale')
   where
          dx      = x' - x
          dy      = y' - y
          scale'  = 1 / sqrt (dx ^ (2 :: Int) + dy ^ (2 :: Int))
  
  
  -- | Checks if floats not too close to each other
  separated :: Float -> Float -> Bool
  separated x y
   = 0.001 < abs (x - y)
  
  
  -- | Gravitational force of one particle to another
  gravitation :: (Float,Float) -> (Float,Float) -> Float
  gravitation (x,y) (x',y')
   = g / sqrt (dx ^ (2 :: Int) + dy ^ (2 :: Int))
   where  dx = x' - x
          dy = y' - y
          g = 1
  
  