haskellrandomparticlesgloss

Haskell Gloss: random starfield not random?


Using Haskell’s Gloss library, I’m trying to simulate a starfield. The visual aspect (drawing ‘stars’ with various speeds and sizes to the screen) is working. However, for some reason the stars aren’t being randomly distributed, resulting in a simulation which has a pattern. I also have this problem with an explosion simulation, but for simplicity’s sake, I’ll leave that one out for now. This is a simplified version of my code so far:

type Position       = (Float, Float)
type Velocity       = (Float, Float)
type Size           = Float
type Speed          = Float
type Drag           = Float
type Life           = Int

type Particle       = (Position, Velocity, Speed, Drag, Life, Size)

-- timeHandler is called every frame by the gloss ‘Play’ function. It's being passed
-- the delta time and the world it needs to update.
timeHandler dt world = world {rndGen = mkStdGen (timer world),
                              timer  = (timer world) + 1,
                              stars  = spawnParticle world (rndGen world) : updateParticles (stars world) dt world}

randomFloat :: StdGen -> Float -> Float -> Float
randomFloat rand min max = fst $ randomR (min, max) rand

spawnParticle :: World -> StdGen -> Particle
spawnParticle world gen = ((pos, (1 * speed, 0), speed, 1, 0, size), snd (split gen))
where pos   = (px', py')
      px'   = randomFloat gen (-600) (-500)
      py'   = randomFloat gen (-250) 250
      speed = size * (randomFloat gen 100 300) -- the smaller a particle, the slower 
      size  = randomFloat gen 0.1 1.3

updateParticles :: [Particle] -> Float -> World -> [Particle]
updateParticles []     _  _     = []
updateParticles (x:xs) dt world | fst(posPart x) > 500 = updateParticles xs dt world
                                | otherwise            = updatedPart : updateParticles xs dt world
    where pos'        = updateParticlePosition dt x world
          updatedPart = (pos', velPart x, speedPart x, 1, 0, sizePart x)

Note: velPart, speedPart etc. are functions to get a property out of a given particle. Again, drawing works fine, so I’ll leave that code out. updateParticlePosition simply adds the velocity to the current position of a star.

I think the problem has something to do with the fact that my random generators are not passed properly, but I’m too confused to come up with a solution… Any help is much appreciated!


Solution

  • This is nothing to do with gloss, just with the semantics of pure random number generation. Re-initializing a new generator with a new seed every time does not give you pseudo-random numbers, because the randomness only comes from the fact that the new generator created by e.g. randomR or split will have seeds very different from the original. You simply have map mkStdGen [0..], as you are simply adding 1 to timer at each step.

    Consider the difference in the following distributions:

    map (fst . randomR (1,1000) . mkStdGen) [0..1000]
    take 1000 $ randomRs (1,1000) (mkStdGen 123)
    

    The first is what you are doing, and the second is proper random numbers.

    The solution is simple, just use split inside your time update function (you already have it inside of spawnParticle) :

    timeHandler dt world = 
      let (newRndGen, g) = split (rndGen world) in 
       world { rndGen = newRndGen 
             , timer  = (timer world) + 1 , 
             , stars  = spawnParticle world g : updateParticles (stars world) dt world
             }
    

    Note that now timer isn't used, and it probably makes more sense to have timer = timer world + dt anyways (the time deltas may not be exactly equal).

    Also keep in mind you shouldn't reuse generators, so if you have many local functions which take a generator as a parameter, you may want something like:

    timeHandler dt world = 
      let (newRndGen:g0:g1:g2:_) = unfoldr (Just . split) (rndGen world) in 
       world { rndGen = newRndGen 
             , timer  = (timer world) + 1 , 
             , stars  = spawnParticle world g0 : updateParticles (stars world) dt world
             , stuff0 = randomStuff0 g1 , stuff1 = randomStuff1 g2 }