I have some fascination with terrain generators, so I whipped up this generator in Python. The way it works is pretty simple, but I figure that I should give an explanation for those who don't want to try and figure it out.
- The initial 2D terrain array is generated with a provided width and height, and then filled entirely with
NoneType
s. - Then the array is iterated over a certain amount of times and "seeds" are placed in random positions. The value of these "seeds" is between a maximum height and a minimum height.
- After this happens, the array is then iterated over another given amount of times, and on each iteration, if a tile is found that isn't a
NoneType
, it's top, bottom, left and right neighbor values are determined by this:tile_value + randint(min_change, max_change)
. This process then repeats until the iteration ends. - The array is iterated over one last time and replaces any remaining
NoneType
s with the minimum height value.
"""
A basic library containing a class for
generating basic terrain data.
"""
from random import randint
class Terrain(object):
"""
Class for generating "realistic" looking terrain.
"""
def __init__(self, max_height, min_height, max_change, min_change, width, height, iterations, seed_iterations):
self.max_height = max_height
self.min_height = min_height
self.max_change = max_change
self.min_change = min_change
self.width = width
self.height = height
self.iterations = iterations
self.seed_iterations = seed_iterations
self.terrain = [[None for _ in range(self.width)] for _ in range(self.height)]
def _seed_terrain(self):
"""
Seed a random integer value at a random location.
"""
for _ in range(self.seed_iterations):
self.terrain[randint(0, self.height-1)][randint(0, self.width-1)] = randint(self.min_height+1, self.max_height-1)
def _iterate_terrain(self):
"""
Loop through the terrain and change each
nearby tile to a value slightly larger or
smaller than the current tile value.
"""
for _ in range(self.iterations):
for row in self.terrain:
for tile in row:
current_row_index = self.terrain.index(row)
current_tile_index = row.index(tile)
upper_index = self.terrain.index(row)+1
lower_index = self.terrain.index(row)-1
right_index = row.index(tile)+1
left_index = row.index(tile)-1
if tile != None:
try:
self.terrain[upper_index][current_tile_index] = tile + randint(self.min_change, self.max_change) if tile >= self.min_height and tile <= self.max_height else tile
self.terrain[lower_index][current_tile_index] = tile + randint(self.min_change, self.max_change) if tile >= self.min_height and tile <= self.max_height else tile
self.terrain[current_row_index][right_index] = tile + randint(self.min_change, self.max_change) if tile >= self.min_height and tile <= self.max_height else tile
self.terrain[current_row_index][left_index] = tile + randint(self.min_change, self.max_change) if tile >= self.min_height and tile <= self.max_height else tile
except IndexError:
continue
def _final_pass(self):
"""
Check to make sure that there are no
NoneTypes left in the array, and if
there are, change them to the minimum
tile value.
"""
for row in self.terrain:
for tile in row:
if tile == None:
self.terrain[self.terrain.index(row)][row.index(tile)] = self.min_height-1
def generate(self):
"""
Generate the final terrain.
"""
self._seed_terrain()
self._iterate_terrain()
self._final_pass()
def render(self, spacing=" "):
"""
Render the completed terrain.
"""
for row in self.terrain:
print spacing.join([str(tile) for tile in row])
def return_terrain(self):
return self.terrain
Few questions that you can answer about my code if you want.
- Is this code Pythonic?
- How can it be made faster? Can it be made Faster?
- Is there a different way I should implement this?
- How can I shorten some lines?