A Method For 'Growing' Rivers In A Randomly Generated World

|| Home ||

In response to a question on rec.games.roguelike.development, I coded up a demo on how one might make randomized rivers that originate in a part of the world that hasn't been generated yet.

The Problem

You are building a world region by region (as the player explores them), so you do not the the layout of the entire planet in advance. How to create long rivers that can span into regions that haven't be created yet? I'll present here an explanation of how this can be done and provide a demo written in Python.

Summary of Algorithm (Although it's simple enough to almost not qualify as an algorithm!)

Suppose you generate a 20X20 area of the world that looks like this:
. . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~          ~ = water
. . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~          . = plains
. . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~          # = trees
. . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~          ^ = hills/mountains
. . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~
^ . . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~
^ ^ . . . . . . . . . . . . ~ ~ ~ ~ ~ ~
^ ^ ^ . . . . . . . . . . . . ~ ~ ~ ~ ~
^ ^ ^ ^ . . . . . . . . . . . . ~ ~ ~ ~
^ ^ ^ ^ ^ . . . # # # . . . . . . ~ ~ ~
^ ^ ^ ^ ^ . . # # . . . . . . . . . ~ ~
^ ^ ^ ^ ^ ^ # # # # . . . . . . . . . ~
^ ^ ^ ^ ^ ^ ^ ^ # # # . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ # # . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . .
Your world generation algorithm determines this lake/ocean will have a river flowing into it. Start drawing a river backwards from the ocean. For my simple demo, I used the following stopping condition for the river: If river was about to cross from an area of higher elevation to an area of lower elevation, terminate the loop. Additionally, I started with a 5% chance of terminating the river and each time the drawing loop iterates, I increase that by 5%.

One particular run of my demo made the following map:

. . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
. . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
. . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
. . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~
. . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~
^ . . . . . . . . . . ~ . ~ ~ ~ ~ ~ ~ ~
^ ^ . . . . . . . . ~ . . . ~ ~ ~ ~ ~ ~
^ ^ ^ . . . . . . ~ . . . . . ~ ~ ~ ~ ~
^ ^ ^ ^ . . . . ~ . . . . . . . ~ ~ ~ ~
^ ^ ^ ^ ^ . ~ ~ # # # . . . . . . ~ ~ ~
^ ^ ^ ^ ~ ~ . # # . . . . . . . . . ~ ~
^ ~ ~ ~ ^ ^ # # # # . . . . . . . . . ~
~ ^ ^ ^ ^ ^ ^ ^ # # # . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ # # . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . .
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . .
If your river reaches the border of a region, again terminate it. (Note that it should also terminate before crossing into region that has already been generated, my demo doesn't do this). At this point, you need to store the fact that a river has terminated at a border of the region. Python is OOP-enabled, so it was quite easy for me to add this information to the region class. I stored the instance of the terrain, it's coordinates and the last move the drawing algorithm was about to make (so I can continue drawing it in the same general direction when it is time to build the next region).

When the player cross into an previously unexplored region, you will have to pass the details of already generated neighbouring regions, providing access to their lists of border features. If a neighbour is null, it hasn't been made yet and can be ignored. In the demo, when a new region is made, we see that it has a neighbour to the east, and that a river terminated at the border (row 12, column 0). After the new region is created, we need to draw a river starting at (row 12, column 19). Using the same algorithm, we draw until we hit a termination condition. In this case, the river started in the hills, so it will continue until it hits a border, or is about to cross from the hills (high elevation) to the plains (lower elevation).

A sample run of my demo produce:
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
# # # # # # # # . . . . . . . . . . . .
# . . . . . . . . . . . . . . . . . . .
# # # # # # # ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
# # # . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
# # # # # # # . . . . . ^ ^ ^ ^ ^ ^ ^ ^
# # # # # # . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^
# # # # # # # # ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
# # . . . . . . . . . . . ^ ^ ^ ^ ^ ^ ^
# # # # # # # . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
# # # # # # # . . . . . . . ^ ^ ^ ^ ^ ~
# # . . . . . . ^ ^ ^ ^ ^ * ^ ^ ^ ^ * ^
# # # # . . . . . . ^ ^ ^ ^ * ^ ^ * ^ ^
# # # # # # # # . . ^ ^ ^ ^ ^ * * ^ ^ ^
# # . . . . . . . . . . . ^ ^ ^ ^ ^ ^ ^
# . . . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
. . . . . . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^
. . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
(The river created is marked with *'s so they stand out a little better).

Putting them side by side:
. . . . . . . . . . . . . . . . . . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
# # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~ ~
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ~ ~ ~ ~ ~ ~ ~ ~
# # # # # # # ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . . . ~ . ~ ~ ~ ~ ~ ~ ~
# # # . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . ~ . . . ~ ~ ~ ~ ~ ~
# # # # # # # . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . ~ . . . . . ~ ~ ~ ~ ~
# # # # # # . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . ~ . . . . . . . ~ ~ ~ ~
# # # # # # # # ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . ~ ~ # # # . . . . . . ~ ~ ~
# # . . . . . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ~ ~ . # # . . . . . . . . . ~ ~
# # # # # # # . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ~ ~ ~ ^ ^ # # # # . . . . . . . . . ~
# # # # # # # . . . . . . . ^ ^ ^ ^ ^ ~ ~ ^ ^ ^ ^ ^ ^ ^ # # # . . . . . . . . .
# # . . . . . . ^ ^ ^ ^ ^ * ^ ^ ^ ^ * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ # # . . . . . . . . .
# # # # . . . . . . ^ ^ ^ ^ * ^ ^ * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . . .
# # # # # # # # . . ^ ^ ^ ^ ^ * * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . . .
# # . . . . . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . . .
# . . . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . . .
. . . . . . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . . .
. . . . . . . . ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ . . . . .
A nice, slightly winding river is created in one region, then continued in the bordering region when it comes time to generate the neighbour.

Notes About The Demo There are three main classes used in the demo:

Terrain - represents an individual square of terrain. Uses fields ch (the character to display), type (the type of terrain and elevation - integer saying how high the terrain is. I used ocean/lake = 0, plains/trees = 1 and hills = 2. An interesting idea would be to store a floating point number and use that to determine where the river should flow from the geography of the land?

Region - stores information about the section of the world this represents. In the demo, this is essentially a collection of Terrain squares and a little additional info used in the demo (such as the border features of this region). In a full game, this would need to store a fair amount of additional information.

RegionGenerator - builds a region of land. Note that in the demo, the regions are cooked and the river in the first region is partially cooked.

The code is offered without warranty or guarantee as an idea for folks writing map generators. If you want to use a substantial part of the code in your game, please ask permission. Here is grow_river.py.txt (named so because browsers sometimes complain about .py extentions, save it your harddrive and rename it grow_river.py before running).

You can reach me at dana@pixelenvy.ca with questions, suggestions, etc.