I need multiple random numbers in different ranges (which could be avoided by multiplying the result of the random number generator with a constant).
I'm making an Asteroid clone in Haskell, and I want to generate randomly spawned enemies. I want to let them spawn at the edge of the screen with the same velocity (so norm of velocity vector equal).
Additional info: When an entity hits the edge of the screen it appears again at the other side, this is why I chose to only let enemies spawn at two of four edges of the screen and made the velocity really determine where you first see them appear.
I looked at System.Random
and ran into difficulties. I figured I needed five random numbers:
- A random number to determine if at this frame a monster should spawn.
- Random x position or random y position (spawns at the edge
- Random number to determine on which side of the screen an enemy spawns
- Random number for x velocity
- Random number (1 or -1) to create a velocity vector with a set norm.
At the start of the game I generate a new StdGen and after that I do this every frame.
Solutions I thought of but thought were bad practice: Instead of passing down generators through methods, create a new generator every time using newStdGen
.
Problem: Ambiguous type variables at all the random calls except for the call to the newRand function (that is also used to update the generator each frame), because Haskell doesn't know which type of number to use.
Sample error:
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
| ^^^
My code:
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)
Aucun commentaire:
Enregistrer un commentaire