patternpythonModerate
Souped-up random walk terrain generator
Viewed 0 times
randomsoupedgeneratorwalkterrain
Problem
I started learning Python a couple of weeks ago, and to test my new knowledge of Python, I decided to use Pygame to code a random walk terrain generator.
```
# Basic random walker program
import random
import pygame
import sys
# Screen width and height
WIDTH = 900
HEIGHT = 900
# Initalize pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Random Walker")
# Tile size
TILE_SIZE = 10
# Tile image variables
VILLAGE = pygame.transform.scale(pygame.image.load("village.png"), (TILE_SIZE, TILE_SIZE))
GRASS = pygame.transform.scale(pygame.image.load("grass.png"), (TILE_SIZE, TILE_SIZE))
WATER = pygame.transform.scale(pygame.image.load("water.png"), (TILE_SIZE, TILE_SIZE))
SAND = pygame.transform.scale(pygame.image.load("sand.png"), (TILE_SIZE, TILE_SIZE))
TREE = pygame.transform.scale(pygame.image.load("tree.png"), (TILE_SIZE, TILE_SIZE))
# World generator class, generates the world
class WorldGenerator(object):
def __init__(self):
self.WORLD_SIZE = 90
self.world = [[WATER for _ in range(self.WORLD_SIZE)] for _ in range(self.WORLD_SIZE)]
self.walker_x = random.randint(15, 25)
self.walker_y = random.randint(15, 25)
# Generate a grass sections of the world
def generate_grass(self):
for _ in range(random.randint(700, 800)):
# Create a new grass tile
try: self.world[self.walker_y][self.walker_x] = GRASS
except IndexError: break
# Change the walker position
self.walker_x += random.choice([-1, -1, -1, 0, 1, 1, 1])
self.walker_y += random.choice([-1, -1, -1, 0, 1, 1, 1])
# Generate a beach section of the world
def generate_beaches(self):
for y in range(self.WORLD_SIZE):
for x in range(self.WORLD_SIZE):
# Add a new sand tile
try:
add_sand = random.choice([True, True, False, False, False])
if add_sand and
```
# Basic random walker program
import random
import pygame
import sys
# Screen width and height
WIDTH = 900
HEIGHT = 900
# Initalize pygame
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Random Walker")
# Tile size
TILE_SIZE = 10
# Tile image variables
VILLAGE = pygame.transform.scale(pygame.image.load("village.png"), (TILE_SIZE, TILE_SIZE))
GRASS = pygame.transform.scale(pygame.image.load("grass.png"), (TILE_SIZE, TILE_SIZE))
WATER = pygame.transform.scale(pygame.image.load("water.png"), (TILE_SIZE, TILE_SIZE))
SAND = pygame.transform.scale(pygame.image.load("sand.png"), (TILE_SIZE, TILE_SIZE))
TREE = pygame.transform.scale(pygame.image.load("tree.png"), (TILE_SIZE, TILE_SIZE))
# World generator class, generates the world
class WorldGenerator(object):
def __init__(self):
self.WORLD_SIZE = 90
self.world = [[WATER for _ in range(self.WORLD_SIZE)] for _ in range(self.WORLD_SIZE)]
self.walker_x = random.randint(15, 25)
self.walker_y = random.randint(15, 25)
# Generate a grass sections of the world
def generate_grass(self):
for _ in range(random.randint(700, 800)):
# Create a new grass tile
try: self.world[self.walker_y][self.walker_x] = GRASS
except IndexError: break
# Change the walker position
self.walker_x += random.choice([-1, -1, -1, 0, 1, 1, 1])
self.walker_y += random.choice([-1, -1, -1, 0, 1, 1, 1])
# Generate a beach section of the world
def generate_beaches(self):
for y in range(self.WORLD_SIZE):
for x in range(self.WORLD_SIZE):
# Add a new sand tile
try:
add_sand = random.choice([True, True, False, False, False])
if add_sand and
Solution
Extract repeated code into functions. Loading the tile images is the same operation with a different file name.
Extract the configuration parameters into constants. You do it properly with
Since there are some many parameters that go into configuring world generation, it makes sense to group them in a single data structure. The structure just contains the require values and nothing else. Your world generation code can take this structure as an argument and generate any world based on the values. Currently, your code can only generate a world with the single set of parameters it is using. Even though none of the functionality changes, you have to dive into the generation functionality just to adjust the likelihood that some operation happens.
Don't manually create your weighted random selection inputs. You knew this was bad, but yet it is still there. A quick search brought up a number of results for ways to do this in a configurable manor. This question has a number of answers that can be used as functions to perform this operation.
The worst part of doing it manually is that it is extremely hard to read and ensure that you did it correctly. The comments brought up this point. There could be 10
You have a number of functions that all iterate over the whole world. Instead, you can write small functions that take in coordinates as arguments. Then they perform the one operation needed to add a specific tile type. Then you have one double for loop that calls each tile function. This will make it easier to read, more efficient and easier to test.
def load_scaled_image(file_name):
return pygame.transform.scale(pygame.image.load(file_name), (TILE_SIZE, TILE_SIZE))
VILLAGE = load_scaled_image("village.png")
GRASS = load_scaled_image("grass.png")
WATER = load_scaled_image("water.png")
SAND = load_scaled_image("sand.png")
TREE = load_scaled_image("tree.png")Extract the configuration parameters into constants. You do it properly with
TILE_SIZE and WORLD_SIZE. But it stops there. The range of locations for the walker. The range of possible number of steps from walker to add grass. All of these are parameters that can be isolated away from the code so that they are more easily adjusted.Since there are some many parameters that go into configuring world generation, it makes sense to group them in a single data structure. The structure just contains the require values and nothing else. Your world generation code can take this structure as an argument and generate any world based on the values. Currently, your code can only generate a world with the single set of parameters it is using. Even though none of the functionality changes, you have to dive into the generation functionality just to adjust the likelihood that some operation happens.
Don't manually create your weighted random selection inputs. You knew this was bad, but yet it is still there. A quick search brought up a number of results for ways to do this in a configurable manor. This question has a number of answers that can be used as functions to perform this operation.
The worst part of doing it manually is that it is extremely hard to read and ensure that you did it correctly. The comments brought up this point. There could be 10
Falses, or there could be 100. This makes a big difference, but makes a reader work very hard to find the answer.You have a number of functions that all iterate over the whole world. Instead, you can write small functions that take in coordinates as arguments. Then they perform the one operation needed to add a specific tile type. Then you have one double for loop that calls each tile function. This will make it easier to read, more efficient and easier to test.
Code Snippets
def load_scaled_image(file_name):
return pygame.transform.scale(pygame.image.load(file_name), (TILE_SIZE, TILE_SIZE))
VILLAGE = load_scaled_image("village.png")
GRASS = load_scaled_image("grass.png")
WATER = load_scaled_image("water.png")
SAND = load_scaled_image("sand.png")
TREE = load_scaled_image("tree.png")Context
StackExchange Code Review Q#74649, answer score: 12
Revisions (0)
No revisions yet.