patternpythonMinor
Generating realistic terrain data - Part 2
Viewed 0 times
realisticpartgeneratingdataterrain
Problem
I've made a "heightmap" terrain generator similar to my previous one, except this one has some improvements, and some new features.
The way this one works is similar to my last one, but slightly different, so I'll run through it again.
```
"""
A basic library containing a class for
generating basic terrain data.
"""
from random import randint
class Terrain(object):
"""
Terrain object for storing and generating
"realistic" looking terrain in an array.
"""
def __init__(self,
min_height, max_height,
min_change, max_change,
data_width, data_height,
seeding_iterations, generation_iterations,
smoothing=True, replace_NoneTypes=True):
self.min_height = min_height
self.max_height = max_height
self.min_change = min_change
self.max_change = max_change
self.data_width = data_width
self.data_height = data_height
self.seeding_iterations = seeding_iterations
self.generation_iterations = generation_iterations
self.smoothing = smoothing
self.replace_NoneTypes = replace_NoneTypes
self.world_data = None
def _assert_arguments(self):
The way this one works is similar to my last one, but slightly different, so I'll run through it again.
- First, a list of
NoneTypesis generated with a setwidthandheight.
- Next, the generator "seeds" certain areas in this list with random values between the minimum world height, and maximum world height.
- After that, the generator iterates over this data a set amount of times, and determines neighbor tile values based on the height value of the current tile. It is determined like this:
tile + randint(self.min_change, self.max_change).
- After the generator is done iterating, it does one loop through the data and replaces any remaining
NoneTypeswith the minimum height value.
- Finally, if
smoothing=True, then the smoother iterates over the data once, and adjusts the values of neighbor tiles to make height distances "less extreme".
```
"""
A basic library containing a class for
generating basic terrain data.
"""
from random import randint
class Terrain(object):
"""
Terrain object for storing and generating
"realistic" looking terrain in an array.
"""
def __init__(self,
min_height, max_height,
min_change, max_change,
data_width, data_height,
seeding_iterations, generation_iterations,
smoothing=True, replace_NoneTypes=True):
self.min_height = min_height
self.max_height = max_height
self.min_change = min_change
self.max_change = max_change
self.data_width = data_width
self.data_height = data_height
self.seeding_iterations = seeding_iterations
self.generation_iterations = generation_iterations
self.smoothing = smoothing
self.replace_NoneTypes = replace_NoneTypes
self.world_data = None
def _assert_arguments(self):
Solution
- You can remove some nesting by
returning early.
- There are other changes that could bring down memory usage, but in
general using generators instead of creating full lists is beneficial
(
xrange below).- List comprehensions as a single function argument can be used without
the rectangular brackets, i.e.
"".join(x for x in []).- Multiplying a list is a bit more concise then the equivalent list
comprehension (
[None] * 4), but beware of sharing if you were tonest that (
[[None] 4] 4 contains the same list four times). Thatwas already mentioned in the first post/answer.
- I've added
_map_tilesmethod to extract the shared "iterate over all
map tiles" behaviour.
- The smoothing function is a bit weird in the sense that the two tests
can easily be merged into a single case and an addition. I don't know
if you wanted maybe a more complex behaviour there, but this is
equivalent and less confusing.
- I'm also not fond of the
IndexErrorhandling, although I can see why
this is easier, as you don't have to bother with generating only valid
coordinates, even then the fact that you abort early and don't check
each of the four cases individually is a bit frustrating to look at.
This was as well mentioned in the first post/answer.
- Since you handle quite a lot of coordinates I'd maybe suggest that, if
you were to continue on this, to introduce more abstractions on top of
the nested list representation in order to handle both the
[x][y]syntax, as well as indexing by coordinate pairs, i.e.
[(x, y)], viaa
Map class (or so). That way you can simplify other code a lot,e.g. via generating a list of coordinates,
zip with new values, thenapply them all at once; basically it would facilitate a more
functional approach. Same, for example, by generating the four
coordinates around a tile in one call, returning
[(x, y+1), ...].All in all:
```
"""
A basic library containing a class for
generating basic terrain data.
"""
from random import randint
class Terrain(object):
"""
Terrain object for storing and generating
"realistic" looking terrain in an array.
"""
def __init__(self,
min_height, max_height,
min_change, max_change,
data_width, data_height,
seeding_iterations, generation_iterations,
smoothing=True, replace_NoneTypes=True):
self.min_height = min_height
self.max_height = max_height
self.min_change = min_change
self.max_change = max_change
self.data_width = data_width
self.data_height = data_height
self.seeding_iterations = seeding_iterations
self.generation_iterations = generation_iterations
self.smoothing = smoothing
self.replace_NoneTypes = replace_NoneTypes
self.world_data = None
def _assert_arguments(self):
"""
Assert the provided arguments to __init__ and
make sure that they are valid.
"""
assert self.max_height > self.min_height, "Maximum height must be larger than minimum height."
assert self.max_change > self.min_change, "Maximum change must be larger than minimum change."
assert self.data_width > 0, "Width must be greater than zero."
assert self.data_height > 0, "Height must be greater than zero."
assert self.seeding_iterations > 0, "Seeding iterations must be greater than zero."
assert self.generation_iterations > 0, "Generation iterations must be greater than zero."
def _generate_inital_terrain(self):
"""
Initalizes the self.world_data array with a
NoneType value.
"""
self.world_data = [[None] * self.data_width
for _ in xrange(self.data_height)]
def _seed_terrain(self):
"""
"Seeds" the terrain by choosing a random spot
in self.world_data, and setting it to a random
value between self.min_height and self.max_height.
"""
for _ in xrange(self.seeding_iterations):
random_x = randint(0, self.data_width - 1)
random_y = randint(0, self.data_height - 1)
self.world_data[random_x][random_y] = randint(
self.min_height, self.max_height
)
def _map_tiles(self, function):
for index_y, tile_row in enumerate(self.world_data):
for index_x, tile in enumerate(tile_row):
function(index_y, tile_row, index_x, tile)
def _generate_iterate(self):
"""
Generates the terrain by iterating n times
and iterating over the world data and changing
terrain values based on tile values.
"""
def generate_tile(index_y, tile_row, index_x, tile):
if tile is None:
return
rand_values = [tile + randint(self.min_change,
self.max_change)
for _ in xrange(
Code Snippets
"""
A basic library containing a class for
generating basic terrain data.
"""
from random import randint
class Terrain(object):
"""
Terrain object for storing and generating
"realistic" looking terrain in an array.
"""
def __init__(self,
min_height, max_height,
min_change, max_change,
data_width, data_height,
seeding_iterations, generation_iterations,
smoothing=True, replace_NoneTypes=True):
self.min_height = min_height
self.max_height = max_height
self.min_change = min_change
self.max_change = max_change
self.data_width = data_width
self.data_height = data_height
self.seeding_iterations = seeding_iterations
self.generation_iterations = generation_iterations
self.smoothing = smoothing
self.replace_NoneTypes = replace_NoneTypes
self.world_data = None
def _assert_arguments(self):
"""
Assert the provided arguments to __init__ and
make sure that they are valid.
"""
assert self.max_height > self.min_height, "Maximum height must be larger than minimum height."
assert self.max_change > self.min_change, "Maximum change must be larger than minimum change."
assert self.data_width > 0, "Width must be greater than zero."
assert self.data_height > 0, "Height must be greater than zero."
assert self.seeding_iterations > 0, "Seeding iterations must be greater than zero."
assert self.generation_iterations > 0, "Generation iterations must be greater than zero."
def _generate_inital_terrain(self):
"""
Initalizes the self.world_data array with a
NoneType value.
"""
self.world_data = [[None] * self.data_width
for _ in xrange(self.data_height)]
def _seed_terrain(self):
"""
"Seeds" the terrain by choosing a random spot
in self.world_data, and setting it to a random
value between self.min_height and self.max_height.
"""
for _ in xrange(self.seeding_iterations):
random_x = randint(0, self.data_width - 1)
random_y = randint(0, self.data_height - 1)
self.world_data[random_x][random_y] = randint(
self.min_height, self.max_height
)
def _map_tiles(self, function):
for index_y, tile_row in enumerate(self.world_data):
for index_x, tile in enumerate(tile_row):
function(index_y, tile_row, index_x, tile)
def _generate_iterate(self):
"""
Generates the terrain by iterating n times
and iterating over the world data and changing
terrain values based on tile values.
"""
def generate_tile(index_y, tile_row, index_x, tile):
if tile is None:
return
rand_values = [tile + randint(self.min_change,
Context
StackExchange Code Review Q#90276, answer score: 2
Revisions (0)
No revisions yet.