Haskell Gloss: случайное звездное поле не случайно?

Используя библиотеку 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)

Примечание: velPart, speedPart и т. д. — это функции для получения свойства данной частицы. Опять же, рисование работает нормально, поэтому я не буду писать этот код. updateParticlePosition просто добавляет скорость к текущему положению звезды.

Я думаю, что проблема как-то связана с тем, что мои генераторы случайных чисел не передаются должным образом, но я слишком запутался, чтобы придумать решение... Любая помощь очень ценится!


person Felix    schedule 28.10.2016    source источник
comment
Помните, что все функции Haskell чисты. Имея это в виду, как можно ожидать, что randomFloat :: StdGen -> Float -> Float -> Float будет давать разные результаты от звонка к звонку?   -  person gspr    schedule 28.10.2016
comment
Я думал, что это должно давать разные результаты, если каждый вызов передается уникальному генератору. По крайней мере, это то, что я пытаюсь сделать..   -  person Felix    schedule 28.10.2016
comment
Вы отбрасываете новое состояние генератора, отбрасывая вторую половину значения, возвращенного из randomR.   -  person gspr    schedule 28.10.2016


Ответы (1)


Это не имеет ничего общего с глянцем, просто с семантикой чистой генерации случайных чисел. Повторная инициализация нового генератора с новым начальным числом каждый раз не дает вам псевдослучайные числа, потому что случайность исходит только из того факта, что новый генератор, созданный, например, randomR или split будут иметь семена, сильно отличающиеся от оригинала. У вас просто есть 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 }
person user2407038    schedule 28.10.2016
comment
Спасибо за ваш ответ! Однако это имеет смысл, звездное поле все же не случайно. Примечательно, что происходит уменьшение размера/скорости частиц от верхней части экрана к нижней... Это потому, что я повторно использую gen в своей функции spawnParticle? - person Felix; 28.10.2016
comment
Я должен был сначала попробовать, прежде чем спрашивать: я применил ту же технику в spawnParticle, что и вы использовали в timeHandler, и теперь она работает! - person Felix; 28.10.2016