# Text-based terrain generator (Part 2)

I've updated my text-based terrain generator a lot. Even though I haven't worked on it all that much. I really just want to know, like usual, is there anything I can improve? How can I shorten the code? What can be made more efficient? Yada yada yada...

#!/usr/bin/env python
from random import choice
from os import system, name

class Tiles(object):
tree = '\033[7;1;32m  \033[0m'
land = '\033[7;32m  \033[0m'
sand = '\033[7;1;33m  \033[0m'
water = '\033[7;1;34m  \033[0m'

class Sizes(object):
small = 10
med1 = 20
med2 = 30
large = 40

sizes = (
Sizes.small, Sizes.med1, Sizes.med2, Sizes.large)

tiles = (
Tiles.land, Tiles.sand, Tiles.tree, Tiles.water)

def generate(world_size):
system('cls' if name == 'nt' else 'clear')
for _ in range(world_size):
print(''.join(choice(tiles) for _ in range(world_size)))

if __name__ == "__main__":
generate(choice(sizes))


Also, this is python 2.7 I just prefer to use print() over print.

Your terran is randomly generated, you may use a "transition" map like this:

transitions = {Tiles.land: [Tiles.sand] +
[Tiles.tree] * 2 +
[Tiles.land] * 5,
Tiles.sand: [Tiles.water, Tiles.sand],
Tiles.tree: [Tiles.tree, Tiles.land],
Tiles.water: [Tiles.water] * 10
}


It read "When you're near a land, you have a chance to peek sand, two to pick tree, and 5 to pick another land".

Then you may want to code something like this to pick a tile according to the neighbors:

def pick_tile_for(world, x, y):
surrounding = []
if x > 0:
surrounding.append(world[x - 1][y])
if y < len(world) - 1:
surrounding.append(world[x][y + 1])
if y > 0:
surrounding.append(world[x][y - 1])
if x < len(world) - 1:
surrounding.append(world[x + 1][y])
surrounding = [tile for tile in surrounding if tile is not None]
if len(surrounding) == 0:
return None
excluded = set(surrounding[0])
for tile in surrounding:
excluded = excluded - set(transitions[tile])
next = list(chain(*[[t for t in transitions[tile] if t not in excluded]
for tile in surrounding]))
return choice(next)


Finally you may want to use this "not so random" tile picker:

def generate(world_size):
system('cls' if name == 'nt' else 'clear')
world = []
for x in range(world_size):
world.append([None for _ in range(world_size)])
world[choice(range(world_size))][choice(range(world_size))] = Tiles.land
for x in range(world_size):
for y in range(world_size):
if world[x][y] is None:
world[x][y] = pick_tile_for(world, x, y)
for x in range(world_size - 1, -1, -1):
for y in range(world_size - 1, -1, -1):
if world[x][y] is None:
world[x][y] = pick_tile_for(world, x, y)
for line in world:
print ''.join(line)


Note that I'm iterating the world twice, one in reverse, because my "first land" is randomly placed, I do not start from a corner, the world is built around the first randomly placed land.

I think there may be a lot of better ways to do this, but it was fun to do. You also may try to read about "flood fill".

Ceveat: I'll never build rivers with this kind of algorithm.

• Could I have an example of output? – Vladimir Putin Jul 13 '14 at 1:11
• I get an error in line 30, it says, TypeError: 'NoneType' object is not iterable. – Vladimir Putin Jul 13 '14 at 1:35
• @VladimirPutin I dropped the whole file here pastebin.com/9tVuZrUc we may have just missed something while copy/pasting, let me know. – Julien Palard Jul 13 '14 at 7:32
• It works! I also noticed something interesting, the larger you make the map size, the more of a chance of getting an ocean with a small landmass was. Vice versa for smaller sizes. – Vladimir Putin Jul 13 '14 at 16:00
• Play with probabilities in the transitions list, you may be able to get what you need. Also starting with more than one point of ground may yield interesting results. Also you may generate "endless" maps, just generating new columns or raws when needed, as the generation is based on neighbors, may be fun to test BUT with the current configuration, expanding ocean ONLY yield oceans ... – Julien Palard Jul 13 '14 at 18:36

You have two errors here:

def generate(world_size):
system('cls' if name =  'nt' else 'clear')
for _ in range(world_size):
print(''.join(choice(tiles) for _ in range(world_size)))


An unexpected indent and = instead of == in the if condition.

Should have been:

def generate(world_size):
system('cls' if name == 'nt' else 'clear')
for _ in range(world_size):
print(''.join(choice(tiles) for _ in range(world_size)))


Other than that, I recommend to follow PEP8, the pep8 command line tool can tell you your style violations, such as not using 2 blank lines before method and class declarations, yada yada. Also, why break the line of the sizes and tiles definitions?