3

我正在尝试实现以下算法,详见此处

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

  2. 在地形上或附近选择一个随机点,以及某个预定最小值和最大值之间的随机半径。仔细选择这个最小值和最大值将使地形崎岖多石或平滑滚动。

  3. 在以该点为中心的地形上升起一座小山,具有给定的半径。

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

但是,一旦我到达必须在地形上选择一个随机点的地步,我就会开始挣扎。这个随机点被包裹在一个 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 !!)
4

3 回答 3

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   
于 2013-11-15T07:50:22.010 回答
2

haskell 最有用的特性之一是仅根据其类型知道函数是确定性的——它使测试变得更加容易。出于这个原因,我将我的设计基于尽可能限制随机性,并用随机变体包装核心非随机函数。使用类型类很容易做到这一点MonadRandom,这是在需要随机值的 haskell 中编写代码的最佳方式。

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

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

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

示例输出:

... ..     ..*.  .***^^^***.   
... ...   .***.  .***^^^*^^*.  
... ..    .*^**......*^*^^^^.  
       .  .***.***.  ..*^^^*.  
      ....*^^***^*.   .^##^*.  
     ..*.*^^^*****.   .^###^..*
      .**^^^^.***...  .*^#^*.**
     .***^##^**..*^^*.*****..**
  ....***^^##^*.*^##^****.   ..
  .......*^###^.*###^****.     
.*********^###^**^##^***....   
*^^^*^##^^^^###^.^^^*. .****.. 
*^^^^####*^####^..**.  .******.
*^^^*####**^###*. ..   .*******
*^#^^^##^***^^*. ...........***
*^^^**^^*..*... ..*******...***
.***..*^^*...  ..*^^#^^^*......
  ...*^##^**.  .*^^#####*.     
    .*^##^**....**^^####*. .***
.. ..*^^^*...*...**^^###^* *^#^
..****^^*. .... ...**###^*.^###
..*******.**.  ..**^^^#^^..^###
 .*****..*^^* ..**^##^**...*^##
.^^^^....*^^*..*^^^##^* ..**^^^
*###^*. .*^**..^###^^^*...*****
^####*.*..*^^*.^###^**.....*.. 
*###^**^**^^^*.*###^. ..   .   
.^^^***^^^^#^*.**^^**.         
 .....***^##^**^^^*^^*.        
      .*^^##^*^##^^^^^.        
      .*^^^^*.^##^*^^*.        
于 2013-11-15T12:10:53.060 回答
1

不行,你逃不掉IO的。也许您可以预先完成所有随机性并重写您的函数以将该随机性作为参数;如果没有,您可以使用MonadRandom或类似方法来跟踪随机种子或将所有内容放入IO.

于 2013-11-15T06:42:02.503 回答