How can I generate random lakes and rivers in my game?

I have a 2D block building game and am trying to make randomly generated lakes and rivers. I have looked into the Perlin noise algorithm, but, I couldn't get it to generate random and nice results.

I have been trying to use the python noise library, but, it didn't create maps very randomly.

Is there some seed function I am missing on that library to make it more random? What variable do I change if I want it to go more random? If possible, give me a less technical answer, with less math and more python terms.

The map is a 2D tiled map. Here is some examples of the non-randomness of the other algorithm. The following code was outputted 3 times in a row. I was randomizing the octaves and frequency with something like this: freq = 16.0 * random.randint(1, 500000) * 0.000001 + 0.5

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1
1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1
1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1
1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1
1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1


This is the code that produced the output above:

"""Writes a 256x256 grayscale simplex noise texture file in pgm format
(see http://netpbm.sourceforge.net/doc/pgm.html)
"""
# $Id: 2dtexture.py 21 2008-05-21 07:52:29Z casey.duncan$

import sys
from noise import pnoise2
import random
octaves = random.randint(1, 500000) * 0.000001 + 0.5
freq = 16.0 * octaves
for y in range(30):
for x in range(40):
n = int(pnoise2(x/freq, y / freq, 1)*10+3)
if n>=1:
n=1
else:
n=0
print n,
print

• I added a little more, but, what do you need to know. – Julian May 14 '12 at 0:56
• You added good info, but we need a little more. What is "not very randomly"? Any screen shot to us see what is the given result, and what is the wanted result? How did you tried it? Any code for you to show us? What's your context? 2D or 3D? Tiled or polygonal? Sorry if all this is too much, But I'm only trying to help. The -1 wasn't mine, if you don't give info, the question will become a unfit for the site and they'll close it. So again, I'm trying to help. – Gustavo Maciel May 14 '12 at 1:11
• +1, now it's a good question :) I'm not good with perlin noise and all procedural generation, but, are you seeding the random object? If i'm not mistaken, its random.seed() So the system time will be used as seed. And instead of octaves = random.randint(1,500000)*.000001+.5 you can try: octaves = random.random() (it have the same result, you'll get a number between 0 and 1, but its much more possibilities than just 500000 numbers.) – Gustavo Maciel May 14 '12 at 1:27
• Thanks +1 :) :) Tried to plus 1 comment but i can't – Julian May 14 '12 at 1:28

Well, as it seems, you are not seeding the random number generator. In python, it can be easily done with just a random.seed().

And I can see too you're generating a number between 1 and 500000 and making it be between 0 and 1. It's a functional method, but it is capped to just 500000 possibilities. You're better with just using random.random() it already generates a number between 0 and 1, but with much more possibilities! If you still need a number between 0.5 and 1.0, as your code suggests, you could just do: (random.random() * 0.5) + 0.5

Your final code should look as follow:

import sys
from noise import pnoise2
import random
random.seed()
octaves = random.random()
# octaves = (random.random() * 0.5) + 0.5
freq = 16.0 * octaves
for y in range(30):
for x in range(40):
n = int(pnoise2(x/freq, y / freq, 1)*10+3)
if n>=1:
n=1
else:
n=0
print n,
print


That's all!

• I'm not clear how seeding has anything to do with it. Random numbers will still be random if you don't seed, but will repeat. The only symptom of not seeding should be the same random sequence every time you run the program (assuming the same seed is used each time). – Tim Holt May 15 '12 at 20:17
• If you're getting the same output three times in a row as mentioned in your question, it is very likely because you have not seeded the random generator. Seed it and see if it fixes the problem. – ashes999 May 15 '12 at 20:30
• Ah I hadn't seen the "3 times in a row" bit - was reading, "Is there some seed function I am missing on that library to make it more random" part. Yes seeding for non repeating randomness :) – Tim Holt May 15 '12 at 20:46

This isn't answering your specific programming question, but consider that creating lakes and rivers isn't about randomly placing blobs of water and strips of water between them. It's about terrain height - about depressions (basins) that turn into lakes, and water that flows from higher to lower spots.

If you want a great example of creating lakes and rivers that make sense, you might check out this blog post -> http://simblob.blogspot.com/2010/09/polygon-map-generation-part-1.html It's a good reference for this kind of thing if your goal is reasonably realistic hydrography.

It's very simple: if you're getting the same map three times in a row (or more), it's because you didn't randomize the seed.

What does this mean?

Computers are inherently deterministic (non-random), so they simulate randomness. It's actually repeatably random (that's why we call it "pseudo random number generator").

How does this work?

When you create a random number, you have the option of giving it a "seed." The important thing is if you always use the same seed, you will always get the same sequence of random numbers, in the same order. Always. This can be good or bad.

In your case, it looks like you're not seeding the random generator, and by default, you're getting the same seed -- it probably uses some component of your date/time. Hence, I recommend you randomize it.

As Gusatavo mentioned in his answer, you need to call random.seed(). The docs state that "if X [the default parameter] is omitted or None, current system time is used." This should be sufficient.