I haven't gushed about Go much on this blog1, but I thought of writing a small series of posts on a little piece of code I cooked up while I was supposed to be doing other, more important things2.
Concept
So I was sitting there, zoning out and browsing one of my favourite subreddits, when I thought about how terrain can be generated. And I thought if we were to take a flat plane that sliced through the mountains at progressively decreasing heights, you'd first get a few isolated spots (the peaks of the mountains), and on the next iteration you'd always have the areas adjacent to these peaks showing up (because mountain tops don't float in thin air).
Computationally, if the map was a 2D array, we could randomly set a few elements as peaks, and then generate the rest of the mountain around those "peak" elements. Of course, with each iteration you would also have the possibility of running into peaks that weren't high enough to be captured at the earlier iteration, so we should keep randomly sprinkling peaks among the (not already assigned) elements in the map.
Pseudocode
The pseudocode for the most basic version would look something like this:
# map initialised to 0s
map[height][width]
for elevation <- 5 to 0
for h <- 0 to height
for w <- 0 to width
# if element is already assigned value, skip
if map[h][w] > 0
continue
# if array element next to higher elevation
# or picked as a peak randomly
if map[h][w] next to elevation + 1 or random()
map[h][w] <- elevation
Seems simple enough, right?
Go Implementation
The initialisations, loops and randomly picking an element to be a peak (and
printing the result out) seem straightforward enough, so let's get those out
of the way first and create our main.go
file:
package main
import (
"fmt"
"math/rand"
"time"
)
const width, height, elevation = 16, 16, 9
// we set the random chance of a peak occuring to 5%
const peakProbability = 5
var fullMap [height][width]int
func main() {
// rand needs to be seeded, so we set the current
// nanosecond timestamp as the seed
rand.Seed(time.Now().UnixNano())
// iterate down from max elevation, assigning vals
for e := elevation; e > 0; e-- {
for h := 0; h < height; h++ {
for w := 0; w < width; w++ {
// if the element has already been
// assigned, skip it
if fullMap[h][w] > 0 {
continue
}
// if the random value meets our criteria,
// it's a peak
if rand.Intn(100) < peakProbability {
fullMap[h][w] = e
}
}
}
}
// print out map
for h := 0; h < height; h++ {
fmt.Println(fullMap[h])
}
}
We run the code with go run main.go
, and get the following output:
To quote Chernobyl3, it's "Not great, not terrible".
In the next part we'll look at a quick and dirty way to implement a way to look at elements adjacent to our current element (the part of our pseudocode that we didn't implement), and assign values to it—and better visualisation of the terrain map.