哈斯克尔光泽:随机星场不是随机的



使用Haskell的Gloss库,我试图模拟一个星场。视觉方面(在屏幕上绘制不同速度和大小的"星星")是有效的。然而,由于某些原因,恒星并不是随机分布的,这导致了一个有模式的模拟。我在爆炸模拟中也遇到了这个问题,但为了简单起见,我现在就不提这个问题了。这是目前为止我的代码的简化版本:

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)

注:velPartspeedPart等是得到给定粒子的性质的函数。同样,绘图工作得很好,所以我将把代码省略。updateParticlePosition只是将速度加到恒星的当前位置。

我认为这个问题与我的随机生成器没有正确通过的事实有关,但我太困惑了,无法想出一个解决方案……任何帮助都非常感谢!

这与光泽无关,只是与纯随机数生成的语义有关。每次用新的种子重新初始化一个新的生成器时,不会给你伪随机数,因为随机性只来自于这样一个事实,即由randomRsplit创建的新生成器将具有与原始非常不同的种子。你只需有map mkStdGen [0..],因为你只需在每一步将1添加到timer

考虑以下分布的差异:

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

第一个是你在做什么,第二个是适当的随机数。

解决方案很简单,只需在您的时间更新函数中使用split(您已经在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
         }

请注意,现在没有使用timer,并且无论如何使用timer = timer world + dt可能更有意义(时间增量可能不完全相等)。

也要记住你不应该重用生成器,所以如果你有很多以生成器作为参数的局部函数,你可能想要这样:

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 }

最新更新