Better Programming

Advice for programmers.

Follow publication

Procedural Map Generation in Python

Joe Godot
Better Programming
Published in
4 min readJul 31, 2022

Photo by Andrew Stutesman on Unsplash

In computing, procedural generation creates data algorithmically instead of manually, typically through a combination of human-generated assets and algorithms coupled with computer-generated randomness and processing power.

We can create a simple map with just a few lines of code using a cellular automaton. The rule of this automaton is that if more than four walls surround a cell, it will create a wall. Otherwise, it will generate a floor. In our example, the only difference between walls and floors is the color: black for the walls, white for the floors.

Let’s start with importing the required libraries and creating the constants for the colors.

import numpy as np
import pygame
import time
# Set colors
WALL_COLOR = (50, 50, 50)
GRID_COLOR = (0, 0, 0)
FLOOR_COLOR = (255, 255, 255)
FLOOR_NEXT_COL = (0, 0, 255)

Now we need to define the update function, which will compute the number of cells surrounding a floor or a wall and decide which asset it should create based on the rule explained before.

We initialize a temporary matrix of zeros which will store the values the cells will have in the next iteration, and then start a for loop that sums, for every cell, the values of the nine cells surrounding it. Then we apply the rule. We assign the FLOOR_NEXT_COL color to the cells that will be transformed into walls in the next round.

We use pygame to draw the cells, and eventually, we set the borders to walls. Otherwise, the map will degenerate to only floors in a few iterations.

def update(screen, cells, size, with_progress=False):
# Create temporary matrix of zeros
temp = np.zeros((cells.shape[0], cells.shape[1]))

for row, col in np.ndindex(cells.shape):
walls = np.sum(cells[row - 1:row + 2, col-1:col+2]) - cells[row, col]
color = FLOOR_COLOR if cells[row, col] == 0 else WALL_COLOR

#Apply rules (if more than 4 walls create a wall, else a floor)
if walls > 4:
temp[row, col] = 1
if with_progress:
color = WALL_COLOR
else:
if cells[row, col] == 1:
if with_progress:
color = FLOOR_NEXT_COL

# Draw rectangles, using as backgorund the screen value.
pygame.draw.rect(screen, color, (col * size, row * size, size - 1, size - 1))

# Set borders to walls
temp[0:60, 0] = 1
temp[0, 0:80] = 1
temp[0:60, 79] = 1
temp[59, 0:80] = 1

return temp

We can now define the main function, as shown below:

def main():    
#Initialize pygame
pygame.init()
# Set size of cells
size=10
# Set size of screen
WIDTH = 800
HEIGHT = 600
# Set dimension of cells and their initial configuration
cells = np.random.choice(2, size=(60, 80), p=[0.38, 0.62])
cells[0:60, 0] = 1
cells[0, 0:80] = 1
cells[0:60, 79] = 1
cells[59, 0:80] = 1

#Init surface/screen
screen = pygame.display.set_mode((WIDTH, HEIGHT))

# Fill the screen with the grid
screen.fill(GRID_COLOR)

update(screen, cells, size)

#Update the full screen
pygame.display.flip()
#Update only portions of the screen
pygame.display.update()

# Initialize running as false, so it won't immediately start the game
running = False
# Create infinite while loop to listen to keys
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
# If space key is pressed, change running in true/flase
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
running = not running
update(screen, cells, size)
pygame.display.update()

if running:
cells = update(screen, cells, size, with_progress=True)
pygame.display.update()
time.sleep(2)

if __name__ == '__main__':
main()

We initialize pygame, set the size of the cells and screen size, and then the initial configuration of the cells. This part is very important. The cells are randomly set to walls or floors, but we need to have more walls than floors to create a smooth map, so we use the numpy random.choice function to set the probability of the walls to 62% and those of the floors to 38%. Then we have to set the borders to walls to avoid the degeneration of the algorithm.

Now we just need to use standard pygame functions to update and display the game. You can change the sleep time if you want the map to be created at a different speed.

Then we launch the code. You can start and stop the automaton using the space bar.

If you want a smooth and homogeneous map, you should wait for more iterations. Otherwise, just a few steps should suffice if you prefer a jagged and fragmented map.

image by author

Sign up to discover human stories that deepen your understanding of the world.

Joe Godot
Joe Godot

Written by Joe Godot

Statistics, machine learning and quantitative finance connoisseur, programming lover, and humanities aficionado.

Write a response