Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions tests/basic_map_operations_test.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import random
import numpy
import unittest
from worldengine.basic_map_operations import distance, index_of_nearest, random_point
from worldengine.basic_map_operations import distance, index_of_nearest


class TestBasicMapOperations(unittest.TestCase):

def setUp(self):
pass

def test_random_point(self):
for seed in [0, 1, 27, 29, 1939, 1982, 2015]:
random.seed(seed)
for n in range(10):
x, y = random_point(100, 200)
self.assertTrue(x >= 0, "x is within boundaries")
self.assertTrue(x < 100, "x is within boundaries")
self.assertTrue(y >= 0, "y is within boundaries")
self.assertTrue(y < 200, "y is within boundaries")

def test_distance(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method random_point() isn't used anymore so I removed it (and this test). It was an almost trivial method anyway.

self.assertAlmostEquals(22.360679774997898, distance((0, 0), (10, 20)))
self.assertAlmostEquals(22.360679774997898, distance((-1, -1), (9, 19)))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was very much unnecessary unless the RNG is expected to be of very dubious quality. I chose to simplify it.

Expand Down
5 changes: 0 additions & 5 deletions worldengine/basic_map_operations.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import math
import random


def random_point(width, height):
return random.randrange(0, width), random.randrange(0, height)


def distance(pa, pb):
Expand Down
7 changes: 4 additions & 3 deletions worldengine/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import os
import numpy
import pickle
import random
import worldengine.generation as geo
from worldengine.common import array_to_matrix, set_verbose, print_verbose
from worldengine.draw import draw_ancientmap_on_file, draw_biome_on_file, draw_ocean_on_file, \
Expand Down Expand Up @@ -390,11 +389,13 @@ def main():
if not os.path.exists(args.FILE):
usage("The specified world file does not exist")

maxseed = 65535 # there is a hard limit somewhere so seeds outside the uint16 range are considered unsafe
if args.seed is not None:
seed = int(args.seed)
assert 0 <= seed <= maxseed, "Seed has to be in the range between 0 and %s, borders included." % maxseed
else:
seed = random.randint(0, 65535)#RNG initialization is done automatically
random.seed(seed)
seed = numpy.random.randint(0, maxseed) # first-time RNG initialization is done automatically
numpy.random.seed(seed)

if args.world_name:
world_name = args.world_name
Expand Down
16 changes: 7 additions & 9 deletions worldengine/drawing_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import math
import numpy
import random
import sys
import time
from worldengine.common import get_verbose
Expand Down Expand Up @@ -133,16 +132,15 @@ def is_inner_border(pos):


def _find_mountains_mask(world, factor):
_mask = [[False for x in range(factor * world.width)] for y in
range(factor * world.height)]
_mask = numpy.full((factor * world.height, factor * world.width), False, dtype=object)
for y in range(factor * world.height):
for x in range(factor * world.width):
if world.is_mountain((int(x / factor), int(y / factor))):
v = len(world.tiles_around((int(x / factor), int(y / factor)),
radius=3,
predicate=world.is_mountain))
if v > 32:
_mask[y][x] = v / 4
_mask[y, x] = v / 4.0 # force conversion to float, Python 2 will *not* do it automatically
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure which behaviour was the intended one here. The ancient map output looks different, I think the mountains are spread a little thinner for the old behaviour int(v/4).

return _mask


Expand Down Expand Up @@ -515,7 +513,7 @@ def _draw_savanna(pixels, x, y):


# TODO: complete and enable this one
def _dynamic_draw_a_mountain(pixels, x, y, w=3, h=3):
def _dynamic_draw_a_mountain(pixels, rng, x, y, w=3, h=3):
# mcl = (0, 0, 0, 255) # TODO: No longer used?
# mcll = (128, 128, 128, 255)
mcr = (75, 75, 75, 255)
Expand All @@ -530,7 +528,7 @@ def _dynamic_draw_a_mountain(pixels, x, y, w=3, h=3):
max_leftborder = int(bottomness * w * 1.33)
if not last_leftborder == None:
max_leftborder = min(max_leftborder, last_leftborder + 1)
leftborder = int(bottomness * w) + random.randint(-2, 2)/2
leftborder = int(bottomness * w) + rng.randint(-2, 2)/2
if leftborder < min_leftborder:
leftborder = min_leftborder
if leftborder > max_leftborder:
Expand All @@ -557,7 +555,7 @@ def _dynamic_draw_a_mountain(pixels, x, y, w=3, h=3):
max_modx = int(bottomness * w * 1.33)
if not last_modx == None:
max_modx = min(max_modx, last_modx + 1)
modx = int(bottomness * w) + random.randint(-2, 2)/2
modx = int(bottomness * w) + numpy.random.randint(-2, 2)/2
if modx < min_modx:
modx = min_modx
if modx > max_modx:
Expand Down Expand Up @@ -603,7 +601,7 @@ def draw_ancientmap(world, target, resize_factor=1,
sea_color=(212, 198, 169, 255),
draw_biome = True, draw_rivers = True, draw_mountains = True,
draw_outer_land_border = False, verbose=get_verbose()):
random.seed(world.seed * 11)
rng = numpy.random.RandomState(world.seed) # create our own random generator

if verbose:
start_time = time.time()
Expand Down Expand Up @@ -893,7 +891,7 @@ def _anti_alias_point(x, y):
if len(world.tiles_around_factor(resize_factor, (x, y),
radius=r,
predicate=on_border)) <= 2:
if random.random() <= .5:
if rng.random_sample() <= .5:
_draw_temperate_forest1(target, x, y, w=w, h=h)
else:
_draw_temperate_forest2(target, x, y, w=w, h=h)
Expand Down
32 changes: 23 additions & 9 deletions worldengine/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,31 +194,45 @@ def _around(x, y, width, height):
def generate_world(w, step):
if isinstance(step, str):
step = Step.get_by_name(step)
seed = w.seed

if not step.include_precipitations:
return w

# Prepare sufficient seeds for the different steps of the generation
rng = numpy.random.RandomState(w.seed) # create a fresh RNG in case the global RNG is compromised (i.e. has been queried an indefinite amount of times before generate_world() was called)
sub_seeds = rng.randint(0, 4294967295, size=100) # sys.maxsize didn't quite work
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the use of this fixed number at all. Does somebody have a better idea? sys.maxint (or whatever it is called) is not available for Python 3; sys.maxsize returns a number that is not compatible with numpy (which takes a uint32).

seed_dict = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice idea using a dict here

'PrecipitationSimulation': sub_seeds[ 0], # after 0.19.0 do not ever switch out the seeds here to maximize seed-compatibility
'ErosionSimulation': sub_seeds[ 1],
'WatermapSimulation': sub_seeds[ 2],
'IrrigationSimulation': sub_seeds[ 3],
'TemperatureSimulation': sub_seeds[ 4],
'HumiditySimulation': sub_seeds[ 5],
'PermeabilitySimulation': sub_seeds[ 6],
'BiomeSimulation': sub_seeds[ 7],
'': sub_seeds[99]
}

# Precipitation with thresholds
PrecipitationSimulation().execute(w, seed)
PrecipitationSimulation().execute(w, seed_dict['PrecipitationSimulation'])

if not step.include_erosion:
return w
ErosionSimulation().execute(w, seed)
ErosionSimulation().execute(w, seed_dict['ErosionSimulation']) # seed not currently used
if get_verbose():
print("...erosion calculated")

WatermapSimulation().execute(w, seed)
WatermapSimulation().execute(w, seed_dict['WatermapSimulation']) # seed not currently used

# FIXME: create setters
IrrigationSimulation().execute(w, seed)
TemperatureSimulation().execute(w, seed)
HumiditySimulation().execute(w, seed)
IrrigationSimulation().execute(w, seed_dict['IrrigationSimulation']) # seed not currently used
TemperatureSimulation().execute(w, seed_dict['TemperatureSimulation'])
HumiditySimulation().execute(w, seed_dict['HumiditySimulation']) # seed not currently used


PermeabilitySimulation().execute(w, seed)
PermeabilitySimulation().execute(w, seed_dict['PermeabilitySimulation'])

cm, biome_cm = BiomeSimulation().execute(w, seed)
cm, biome_cm = BiomeSimulation().execute(w, seed_dict['BiomeSimulation']) # seed not currently used
for cl in cm.keys():
count = cm[cl]
if get_verbose():
Expand Down
3 changes: 1 addition & 2 deletions worldengine/plates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# extension which is not available when using this project from jython

import platec
import random
import time
import numpy

Expand Down Expand Up @@ -64,7 +63,7 @@ def world_gen(name, width, height, seed, num_plates=10, ocean_level=1.0,

if verbose:
start_time = time.time()
add_noise_to_elevation(world, random.randint(0, 4096))
add_noise_to_elevation(world, numpy.random.randint(0, 4096)) # uses the global RNG; this is the very first call to said RNG - should that change, this needs to be taken care of
if verbose:
elapsed_time = time.time() - start_time
print("...plates.world_gen: elevation noise added. Elapsed time " +
Expand Down
1 change: 1 addition & 0 deletions worldengine/simulations/erosion.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def _numpy_to_matrix(numpy_array):

This is used because currently we do not know how to serialize numpy
arrays :( with pickle. In the future we will use pytables/hdf5"""
# TODO: Is this still true? Pickle didn't seem to cause problems for me. /tcld

width = numpy_array.shape[0]
height = numpy_array.shape[1]
Expand Down
4 changes: 2 additions & 2 deletions worldengine/simulations/hydrology.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def droplet(world, pos, q, _watermap):

_watermap_data = numpy.zeros((world.height, world.width), dtype=float)
for i in range(n):
x, y = world.random_land()
if True and world.precipitation['data'][y, x] > 0:
x, y = world.random_land() # will return None for x and y if no land exists
if x is not None and world.precipitation['data'][y, x] > 0:
droplet(world, (x, y), world.precipitation['data'][y, x],
_watermap_data)
_watermap = dict()
Expand Down
2 changes: 1 addition & 1 deletion worldengine/simulations/irrigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class IrrigationSimulation(object):
def is_applicable(world):
return world.has_watermap() and (not world.has_irrigation())

def execute(self, world, seed):#seed is currently not used
def execute(self, world, seed):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is fine, execute is part of an interface, in the other implementations seed is used

world.irrigation = self._calculate(world)

@staticmethod
Expand Down
6 changes: 3 additions & 3 deletions worldengine/simulations/permeability.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from worldengine.simulations.basic import find_threshold_f
from noise import snoise2
import random
import numpy


Expand All @@ -21,8 +20,9 @@ def execute(self, world, seed):

@staticmethod
def _calculate(seed, width, height):
random.seed(seed * 37)
base = random.randint(0, 4096)
rng = numpy.random.RandomState(seed) # create our own random generator
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of permanently modifying the global RNG (e.g. numpy.random.randint()) the sub-steps now use their own RNG whenever easily possible (and they don't modify the seeds anymore due to that now happening in generation.py).

This makes the main generation mostly independent of the order in which steps are done. In addition, should a GUI ever be able to let the user modify seeds for different steps of the generation, that seed would then actually be used instead of being multiplied by an arbitrary number first.

base = rng.randint(0, 4096)

perm = numpy.zeros((height, width), dtype=float)

octaves = 6
Expand Down
6 changes: 3 additions & 3 deletions worldengine/simulations/precipitation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import random
import time
import numpy
from noise import snoise2
Expand Down Expand Up @@ -32,9 +31,10 @@ def execute(self, world, seed):
@staticmethod
def _calculate(seed, width, height):
"""Precipitation is a value in [-1,1]"""
rng = numpy.random.RandomState(seed) # create our own random generator
base = rng.randint(0, 4096)

border = width / 4
random.seed(seed * 13)
base = random.randint(0, 4096)
precipitations = numpy.zeros((height, width), dtype=float)

octaves = 6
Expand Down
5 changes: 2 additions & 3 deletions worldengine/simulations/temperature.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from worldengine.simulations.basic import find_threshold_f
import random
import numpy


Expand Down Expand Up @@ -31,8 +30,8 @@ def _calculate(world, seed, elevation, mountain_level):
width = world.width
height = world.height

random.seed(seed * 7)
base = random.randint(0, 4096)
rng = numpy.random.RandomState(seed) # create our own random generator
base = rng.randint(0, 4096)
temp = numpy.zeros((height, width), dtype=float)

from noise import snoise2
Expand Down
12 changes: 6 additions & 6 deletions worldengine/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
WarmTemperateWetForest, TropicalDesert, TropicalDesertScrub, TropicalDryForest, \
TropicalMoistForest, TropicalRainForest, TropicalThornWoodland, TropicalWetForest, \
TropicalVeryDryForest, biome_index_to_name, biome_name_to_index
from worldengine.basic_map_operations import random_point
import worldengine.protobuf.World_pb2 as Protobuf
from worldengine.step import Step
from worldengine.common import _equal
Expand Down Expand Up @@ -332,11 +331,12 @@ def contains(self, pos):
#

def random_land(self):
x, y = random_point(self.width, self.height)
if self.ocean[y, x]:#TODO: this method should get a safer/quicker way of finding land!
return self.random_land()
else:
return x, y
if self.ocean.all():
return None, None # return invalid indices if there is no land at all
lands = numpy.invert(self.ocean)
lands = numpy.transpose(lands.nonzero()) # returns a list of tuples/indices with land positions
y, x = lands[numpy.random.randint(0, len(lands))] # uses global RNG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we return None or (None, None) here when there is no land?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it.

return x, y

def is_land(self, pos):
return not self.ocean[pos[1], pos[0]]#faster than reversing pos or transposing ocean
Expand Down