Неоднозначная ошибка типа & Несколько разных случайных значений в одной и той же функции - PullRequest
0 голосов
/ 21 октября 2018

Мне нужно несколько случайных чисел в разных диапазонах (этого можно избежать, умножив результат генератора случайных чисел на константу).

Я делаю клон Астероидов в Haskell, и я хочугенерировать случайно порожденных врагов.Я хочу, чтобы они появлялись на краю экрана с той же скоростью (чтобы норма вектора скорости была равна).

Дополнительная информация: Когда объект попадает на край экрана, он снова появляется на другой сторонеИменно поэтому я решил позволить врагам появляться только на двух из четырех краев экрана и заставил скорость действительно определять, где вы впервые видите их появление.

Я посмотрел на System.Random и столкнулся с трудностями.Я подумал, что мне нужно пять случайных чисел:

  1. Случайное число, чтобы определить, должен ли монстр появиться в этом кадре.
  2. Случайное положение x или случайное положение y (появляется на краю
  3. Случайное число, чтобы определить, на какой стороне экрана появляется враг
  4. Случайное число для скорости x
  5. Случайное число (1 или -1) для создания вектора скорости сустановить норму.

В начале игры я генерирую новый StdGen, и после этого я делаю это каждый кадр.

Решения, о которых я думал, но считал их плохой практикой: вместопередавая генераторы через методы, каждый раз создавая новый генератор, используя newStdGen.

Я также думал о вызове функции newRand для всех нужных мне случайных чисел, но если бы у меня была функция, которая требовала двух случайных чиселв одном и том же диапазоне два случайных числа будут идентичны, поскольку идентичный ввод всегда дает одинаковый вывод в Haskell.

Проблема: Неоднозначный тип varможно использовать для всех случайных вызовов, кроме вызова функции newRand (которая также используется для обновления генератора каждого кадра), потому что Haskell не знает, какой тип номера использовать.

Примерошибка:

src\Controller.hs:45:56: error:
    * Ambiguous type variable `a0' arising from the literal `0.0'
      prevents the constraint `(Fractional a0)' from being solved.
      Relevant bindings include
        axis :: a0 (bound at src\Controller.hs:45:25)
      Probable fix: use a type annotation to specify what `a0' should be.
      These potential instances exist:
        instance HasResolution a => Fractional (Fixed a)
          -- Defined in `Data.Fixed'
        instance Fractional Double -- Defined in `GHC.Float'
        instance Fractional Float -- Defined in `GHC.Float'
        ...plus three instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    * In the expression: 0.0
      In the first argument of `randomR', namely `(0.0, 1.0)'
      In the second argument of `($)', namely `randomR (0.0, 1.0) gen'
   |
45 |                         axis = signum $ fst $ randomR (0.0, 1.0) gen
   |                                                        ^^^

Мой код:

newRand :: StdGen -> (Float, Float) -> (Float, StdGen)
newRand gen (a, b) = randomR (a, b) gen

genEnemies :: StdGen -> Float -> [Moving Enemy]
genEnemies gen time | rand > 995 = [Moving (x, y) (vx, vy) defaultEnemy]
                    | otherwise  = []
                  where rand = fst $ newRand (0, 1000) gen
                        x | axis < 0.5  = fst $ randomR (0.0, width) gen
                          | otherwise   = 0
                        y | axis >= 0.5 = fst $ randomR (0.0, height) gen
                          | otherwise   = 0
                        axis = signum $ fst $ randomR (0.0, 1.0) gen
                        vx   = fst $ randomR (-20.0, 20.0) gen
                        vy   = sgn * sqrt (400 - vx*vx)                  
                        sgn  = (signum $ fst $ randomR (-1.0, 1.0) gen)

1 Ответ

0 голосов
/ 22 октября 2018

Обычный шаблон, когда вы хотите сгенерировать несколько случайных чисел в Haskell, выглядит следующим образом:

foo :: StdGen -> (StdGen, (Int, Int, Int))
foo g0 = let
    (val1, g1) = randomR (0, 10) g0
    (val2, g2) = randomR (0, 10) g1
    (val3, g3) = randomR (0, 10) g2
    in (g3, (val1, val2, val3))

Например, в ghci:

System.Random> foo (mkStdGen 0)
(1346387765 2103410263,(7,10,2))

Вы можете видеть, что он вернул триразные цифры - в отличие от того, что вы получили бы, если бы вы каждый раз звонили randomR с g0, как вы это делали в своем коде.

Надеемся, вы поймете шаблон: позвоните randomR с нужным вам диапазоном иа StdGen;используйте значение, которое оно вернуло, как ваш случайный фрагмент, и StdGen, которое он вернул, в качестве ввода при следующем вызове randomR.Также важно, чтобы вы возвращали обновленный StdGen из вашей случайной функции, чтобы вы могли продолжить паттерн в последующих вызовах.

Позже вы можете посмотреть на монады, в частности RandT, который может абстрагировать процесс подачи последних обновленных StdGen в следующий вызов randomR.Образец стиля RandT выглядит следующим образом:

foo' :: MonadRandom m => m (Int, Int, Int)
foo' = do
    val1 <- getRandomR (0, 10)
    val2 <- getRandomR (0, 10)
    val3 <- getRandomR (0, 10)
    return (val1, val2, val3)

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

...