问题
I am trying to make a version of the Voltorb game from Pokemon Gold and Silver in Haskell. Now for generation of the board, I want to have a list of (l,r,v) triplets where l is the line, r is the row and v is the value of the field.
Values l and r are implemented with list comprehension since they should be the same every time. As for v though I can't find an option to implement it so that it is 0,1,2 or 3 "randomly" (I know that Haskell is purely functional and there is no true randomness, that's part of the reason why I struggle with this).
If anyone could help with this I'd be very grateful. If you could also maybe give a short explanation as to why that solution works, that would help me a lot.
My current implementation of l and r:
field n = [(l,r,v) | l <- [0..n], r <- [0..n]]
回答1:
If I understand the question correctly, there is to be one random value per (Int, Int) board position. So the problem cannot be solved by adding a third clause into the comprehension list, such as:
field n = [(l,r,v) | l <- [0..n], r <- [0..n], v <- someRandomStuff]
as the length of the field
expression would then be (n+1)x(n+1)x(length of random stuff), and what you want is just (n+1)x(n+1).
A possibility consists in operating in two steps:
- generating the required (n+1)*(n+1) random values between 0 and 3
- combining that with the (l,r) values
I assume the reader understands pseudo-random number generation from imperative languages.
Given a seed, you can use a throw-away random number generator returned by function mkStdGen to generate the random values, using function randomRs. Let's use a ghci
session as a testbed.
Regarding step 1:
λ> import System.Random
λ> :t randomRs
randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]
λ>
λ> seed1=42
λ>
λ> getVSeq n seed = let rng0 = mkStdGen seed in take ((n+1)^2) (randomRs (0,3) rng0)
λ>
λ> getVSeq 5 seed1
[1,1,3,0,2,1,0,1,0,1,3,1,2,0,2,3,1,1,3,2,0,2,2,0,2,0,0,0,1,0,2,1,0,2,0,1]
λ>
λ> length $ getVSeq 5 seed1
36
λ> field0 n = [(l,r) | l <- [0..n], r <- [0..n]]
λ> field0 5
[(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(1,0),(1,1),(1,2),(1,3),(1,4),(1,5),(2,0),(2,1),(2,2),(2,3),(2,4),(2,5),(3,0),(3,1),(3,2),(3,3),(3,4),(3,5),(4,0),(4,1),(4,2),(4,3),(4,4),(4,5),(5,0),(5,1),(5,2),(5,3),(5,4),(5,5)]
λ>
λ>
λ>
λ> length $ field0 5
36
λ>
Now regarding step 2, function zip
almost solves our problem, except we do not exactly get triplets:
λ>
λ> sol0 n seed = zip (field0 n) (getVSeq n seed)
λ> sol0 5 seed1
[((0,0),1),((0,1),1),((0,2),3),((0,3),0),((0,4),2),((0,5),1),((1,0),0),((1,1),1),((1,2),0),((1,3),1),((1,4),3),((1,5),1),((2,0),2),((2,1),0),((2,2),2),((2,3),3),((2,4),1),((2,5),1),((3,0),3),((3,1),2),((3,2),0),((3,3),2),((3,4),2),((3,5),0),((4,0),2),((4,1),0),((4,2),0),((4,3),0),((4,4),1),((4,5),0),((5,0),2),((5,1),1),((5,2),0),((5,3),2),((5,4),0),((5,5),1)]
λ>
So we need to massage the result of sol0
a little bit:
λ>
λ> sol1 n seed = let flatten = (\((a,b),c) -> (a,b,c)) in map flatten (sol0 n seed)
λ> sol1 5 seed1
[(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]
λ>
So this is what I understood you wanted. If this is the sole use of random numbers in your application, this could be good enough. Otherwise, I am afraid there is a need to build some knowledge about random number generation in a Haskell functional programming context. You might want to start here or there.
Also, as mentioned by Thomas M. DuBuisson, this has been addressed in several SO questions. You can use the local search engine. Here is one of the recent ones for example.
What if you need to get your generator back for reuse ?
In that case, you need to have a function that takes a pre-built generator, and returns BOTH the "field" triplet list and the (final state of the) generator as a (list,finalRng) pair.
You can subcontract the hard (random) work to a function that returns another, simpler pair with just the list of v values and the final state of the generator. That function can be written in recursive fashion, as show below.
import System.Random
import Control.Monad.Random
-- returns the random v values AND the final state of the generator
seqAndGen :: RandomGen tg => (Int,Int) -> Int-> tg -> ([Int], tg)
seqAndGen range count rng0 =
if (count <= 0)
then ([],rng0)
else
let (v,rng1) = randomR range rng0
nextSeq = seqAndGen range (count-1) rng1 -- recursive call
in
(v:(fst nextSeq), snd nextSeq)
-- returns the "field" values AND the final state of the generator
fieldAndGen :: RandomGen tg => Int -> tg -> ([(Int,Int,Int)], tg)
fieldAndGen n rng0 =
let field0 = [(l,r) | l <- [0..n], r <- [0..n]]
range = (0,3) -- at that level, range gets hardwired
count = (n+1)*(n+1) -- number of field/board positions
pair = seqAndGen range count rng0 -- the hard work
vSeq = fst pair
endRng = snd pair
flatten = \((a,b),c) -> (a,b,c)
field = map flatten (zip field0 vSeq)
in
(field, endRng)
Main program:
main = do
let mySeed = 42
n = 5
putStrLn $ "seed=" ++ (show mySeed) ++ " " ++ "n=" ++ (show n)
-- get a random number generator:
let rng0 = mkStdGen mySeed
let (field, endRng) = fieldAndGen n rng0
fieldv = map (\(a,b,c) -> c) field
putStrLn $ "endRng = " ++ (show endRng)
putStrLn $ "field = " ++ (show field)
Program output:
seed=42 n=5
endRng = 1388741923 1700779863
field = [(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]
Note that there is a possible variant where instead of passing around the generator, you pass around the infinite list of v values as generated by function randomRs. It is handy to use function splitAt for such a purpose. But that assumes you are using randomness just for v values and nothing else, so it is a bit less general and less flexible.
回答2:
Here's a short way to separate (1) pseudo-random pure code from (2) random seeding of the pseudo-random generator:
--Get the generator in IO monad
main :: IO ()
main = do
g <- getStdGen
print $ buildGrid g 5
--Keep as much code as possible pure
buildGrid :: StdGen -> Int -> [(Int, Int, Int)]
buildGrid g n = zipWith ($) ((,,) <$> [0..n] <*> [0..n])
(take ((n+1) * (n+1)) $ randomRs (0,3) g)
Or you can keep your original list comprehension, but in that case you have to use a language extension:
{-# LANGUAGE TupleSections #-}
...
buildGrid g n = zipWith ($) [(y,x,) | y <- [0..n], x <- [0..n]]
(take ((n + 1) * (n + 1)) $ randomRs (0,3) g)
来源:https://stackoverflow.com/questions/60350962/another-question-about-random-numbers-in-haskell