如何阻止随机性在Haskell中传播我的代码?

时间:2013-11-15 06:34:15

标签: haskell random monads

我正在尝试实施以下算法,详见here

  
      
  1. 从平坦地形开始(将所有高度值初始化为零)。

  2.   
  3. 在地形上或附近选择一个随机点和一个随机半径   介于某个预定的最小值和最大值仔细选择   这个最小值和最大值将使地形变得粗糙,岩石或光滑   轧制。

  4.   
  5. 在以点为中心的地形上升起一座小山   半径。

  6.   
  7. 返回第2步,根据需要重复多次。数字   选择的迭代次数将影响地形的外观。

  8.   

然而,一旦我到达必须在地形上选择一个随机点的点,我就开始挣扎。这个随机点包含在IO monad中,然后将其传递给我的函数链。

我可以在某个时间点关闭IO,如果是,我该如何找到这一点?

以下是我的(破损)代码。我将不胜感激任何关于改进它/阻止随机性感染一切的建议。

type Point = (GLfloat, GLfloat, GLfloat)
type Terrain = [Point]

flatTerrain :: Double -> Double -> Double -> Double -> Terrain
flatTerrain width length height spacing =
    [(realToFrac x, realToFrac y, realToFrac z)
         | x <- [-width,-1+spacing..width], y <- [height], z <- [-length,-1+spacing..length]]

hill :: Terrain -> Terrain
hill terrain = hill' terrain 100
               where hill' terrain 0 = terrain
                     hill' terrain iterations = do
                       raised <- raise terrain
                       hill' (raise terrain) (iterations - 1)
                     raise terrain = do
                       point <- pick terrain
                       map (raisePoint 0.1 point) terrain
                     raisePoint r (cx,cy,cz) (px,py,pz) = 
                         (px, r^2 - ((cx - px)^2 + (cz - pz)^2), pz)

pick :: [a] -> IO a
pick xs = randomRIO (0, (length xs - 1)) >>= return . (xs !!)

3 个答案:

答案 0 :(得分:6)

该算法表明你需要iterate and in each iteration select a random number and update the terrain可以被视为generate a list of random points and use this list to update the terrain,即迭代生成随机数==随机数列表。

所以你可以这样做:

selectRandomPoints :: [Points] -> Int -> IO [Points] -- generate Int times random points
updateTerrain :: Terrain -> [Points] -> Terrain

-- somewhere in IO
do
  pts <- selectRandomPoints allPts iterationCount
  let newTerrain = updateTerrain t pts   

答案 1 :(得分:2)

haskell最有用的功能之一是根据其类型知道一个函数是确定性的 - 它使测试更容易。出于这个原因,我的设计基于尽可能限制随机性,并使用随机变量包装核心非随机函数。这可以通过MonadRandom类型类轻松完成,这是在haskell中编写需要随机值的代码的最佳方式。

为了好玩,我写了一个山地生成器的控制台版本。这是非常基本的,有很多硬编码常量。但是,它确实提供了一个非常酷的ascii地形生成器:)

请注意,在我的解决方案中,所有计算都是在纯粹的非随机函数中隔离的。然后可以很容易地测试,因为结果是确定性的。尽可能少地发生在IO monad。

import Control.Monad
import Control.Monad.Random
import Data.List
import Data.Function (on)

type Point = (Double, Double, Double)
type Terrain = [Point]

-- Non random code

flatTerrain :: Double -> Double -> Double -> Double -> Terrain
flatTerrain width length height spacing = [(realToFrac x, realToFrac y, realToFrac z)
         | x <- [-width,-width+spacing..width], y <- [height], z <- [-length,-length+spacing..length]]

-- simple terrain displayer, uses ascii to render the area.
-- assumes the terrain points are all separated by the same amount
showTerrain :: Terrain -> String
showTerrain terrain = unlines $ map (concat . map showPoint) pointsByZ where
  pointsByZ = groupBy ((==) `on` getZ) $ sortBy (compare `on` getZ) terrain
  getZ (_, _, z) = z
  getY (_, y, _) = y

  largest = getY $ maximumBy (compare `on` getY) terrain
  smallest = getY $ minimumBy (compare `on` getY) terrain
  atPC percent = (largest - smallest) * percent + smallest

  showPoint (_, y, _)
    | y < atPC (1/5) = " "
    | y < atPC (2/5) = "."
    | y < atPC (3/5) = "*"
    | y < atPC (4/5) = "^"
    | otherwise = "#"

addHill :: Double -- Radius of hill
        -> Point -- Position of hill
        -> Terrain -> Terrain
addHill radius point = map (raisePoint radius point) where
  raisePoint :: Double -> Point -> Point -> Point
  -- I had to add max py here, otherwise new hills destroyed the
  -- old hills with negative values.
  raisePoint r (cx,cy,cz) (px,py,pz) = (px, max py (r^2 - ((cx - px)^2 + (cz - pz)^2)), pz)

-- Some random variants. IO is an instance of MonadRandom, so these function can be run in IO. They
-- can also be run in any other monad that has a MonadRandom instance, so they are pretty flexible.

-- creates a random point. Note that the ranges are hardcoded - an improvement would
-- be to be able to specify them, either through parameters, or through reading from a Reader
-- monad or similar
randomPoint :: (MonadRandom m) => m Point
randomPoint = do
  x <- getRandomR (-30, 30)
  y <- getRandomR (0,10)
  z <- getRandomR (-30, 30)
  return (x, y, z)

addRandomHill :: (MonadRandom m) => Terrain -> m Terrain
addRandomHill terrain = do
  radius <- getRandomR (0, 8) -- hardcoded again
  position <- randomPoint
  return $ addHill radius position terrain

-- Add many random hills to the Terrain
addRandomHills :: (MonadRandom m) => Int -> Terrain -> m Terrain
addRandomHills count = foldr (>=>) return $ replicate count addRandomHill

-- testing code

test hillCount = do
  let terrain = flatTerrain 30 30 0 2
  withHills <- addRandomHills hillCount terrain
  -- let oneHill = addHill 8 (0, 3, 0) terrain
  -- putStrLn $ showTerrain oneHill
  putStrLn $ showTerrain withHills

main = test 200

示例输出:

... ..     ..*.  .***^^^***.   
... ...   .***.  .***^^^*^^*.  
... ..    .*^**......*^*^^^^.  
       .  .***.***.  ..*^^^*.  
      ....*^^***^*.   .^##^*.  
     ..*.*^^^*****.   .^###^..*
      .**^^^^.***...  .*^#^*.**
     .***^##^**..*^^*.*****..**
  ....***^^##^*.*^##^****.   ..
  .......*^###^.*###^****.     
.*********^###^**^##^***....   
*^^^*^##^^^^###^.^^^*. .****.. 
*^^^^####*^####^..**.  .******.
*^^^*####**^###*. ..   .*******
*^#^^^##^***^^*. ...........***
*^^^**^^*..*... ..*******...***
.***..*^^*...  ..*^^#^^^*......
  ...*^##^**.  .*^^#####*.     
    .*^##^**....**^^####*. .***
.. ..*^^^*...*...**^^###^* *^#^
..****^^*. .... ...**###^*.^###
..*******.**.  ..**^^^#^^..^###
 .*****..*^^* ..**^##^**...*^##
.^^^^....*^^*..*^^^##^* ..**^^^
*###^*. .*^**..^###^^^*...*****
^####*.*..*^^*.^###^**.....*.. 
*###^**^**^^^*.*###^. ..   .   
.^^^***^^^^#^*.**^^**.         
 .....***^##^**^^^*^^*.        
      .*^^##^*^##^^^^^.        
      .*^^^^*.^##^*^^*.        

答案 2 :(得分:1)

不,你无法逃脱IO。也许你可以预先做所有的随机性并重写你的函数,将随机性作为参数;如果没有,您可以使用MonadRandom或类似内容跟踪随机种子,或者只将所有内容放入IO