HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Text-based terrain generator (Part 2)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
textpartgeneratorbasedterrain

Problem

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.

Solution

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  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.

Code Snippets

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
               }
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)
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)

Context

StackExchange Code Review Q#56864, answer score: 5

Revisions (0)

No revisions yet.