As a first step, you can take a look how to simplify the while
loop inside the for
loop. One option is to use Seq.initInfinite
to generate a sequence that will give you any number of random X, Y coordinates. Then you can use Seq.find
to find the first one that refers to an empty board field.
I also changed isEmpty
to take a tuple (so that you can pass as argument to Seq.find
using partial function application) and I changed some names to follow more standard F# style (you generally wouldn't use hungarian naming notation):
let isEmpty (x, y) = board.[x,y] = -1
let rnd = new System.Random()
for i = 0 to obstacleCount do
let x, y =
// Generate infinite sequence of random X Y coordinates
Seq.initInfinite (fun _ -> rnd.Next(width), rnd.Next(height))
// Find first coordinate that refers to empty field
|> Seq.find isEmpty
// We still have mutation here
board.[x,y] <- Obstacle
I think this is quite elegant functional solution. It may be a bit slower than the imperative solution, but the point is that functional style makes it easier to write & change the implementation once you learn it (You can always use imperative style as optimization).
To avoid all mutable state, you'll need to generate locations for obstacles first and then initialize the array. For example, you could recursively add new coordinates to a set until it has the required length. Then you can generate array using Array2D.init
:
let rec generateObstacles obstacles =
if Set.count obstacles = obstacleCount then obstacles
else
// Try generating new coordinate and add it to the set
// (if it is already included, this doesn't do anything)
obstacles
|> Set.add (rnd.Next(width), rnd.Next(height))
|> generateObstacles
let obstacles = generateObstacles Set.empty
Array2D.init width height (fun x y ->
if obstacles.Contains(x, y) then Obstacle else Empty)
This isn't really shorter and it will be a bit slower, so I'd stick to the first solution. However, it is a nice exercise showing recursion and sets...