I’m using a pandas dataframe to store a dynamic 2D game map for a rougelike style game map editor. The player can draw and erase rooms. I need to draw walls around these changing rooms.
I have this:
0 1 2 3 4 5 6 0 . . . x . . 1 . . x x . . 2 . x x x . . 3 . . . . . . 4 . . . . . .
And need this:
0 1 2 3 4 5 6 0 . # # x # . 1 # # x x # . 2 # x x x # . 3 # # # # # . 4 . . . . . .
What is the most efficient way to do this?
So far I followed the approach outlined here, but this leaves me with some nested if
and for
before and after the lambda
. As I have to check first if a cell is currently dug out. Then check all eight neighbors if they are dug out or not before changing the matching cells. This really takes a tool on the frame rate. I can’t be the first to struggle with something like this, but got stuck at finding a solution.
I was hoping to find a way by applying mask
or a similar binary comparison. Still, I have no idea how to efficiently do the neighbor checks without falling back into nested loops.
Advertisement
Answer
What you want to do is called a binary dilation. You can do this on the underlying numpy array with scipy.ndimage.morphology.binary_dilation
:
from scipy.ndimage.morphology import binary_dilation import numpy as np a = df.eq('x').to_numpy() # [[False False True True True False] # [False True True True True False] # [ True True True True True False] # [False True True True False False] # [False False False False False False]] df = pd.DataFrame(np.where(binary_dilation(a), 'x', df))
output:
0 1 2 3 4 5 0 . . x x x . 1 . x x x x . 2 x x x x x . 3 . x x x . . 4 . . . . . .
Now to get a different symbol, you can use a more complex mask (binary_dilation(a)^a
) with a XOR operation (^
):
a = df.eq('x').to_numpy() df = pd.DataFrame(np.where(binary_dilation(a)^a, '#', df))
output:
0 1 2 3 4 5 0 . . # x # . 1 . # x x # . 2 # x x x # . 3 . # # # . . 4 . . . . . .
all neighbors
Use a different structuring element (here a 3×3 matrix of 1s):
from scipy.ndimage.morphology import binary_dilation a = df.eq('x').to_numpy() kernel = np.ones((3,3)) df = pd.DataFrame(np.where(binary_dilation(a, kernel)^a, '#', df))
output:
0 1 2 3 4 5 0 . # # x # . 1 # # x x # . 2 # x x x # . 3 # # # # # . 4 . . . . . .
other kernels
You can easily adapt the code to have any combination of neighbors
example: top left
kernel = np.array([[1, 1, 0], [1, 1, 0], [0, 0, 0]]) 0 1 2 3 4 5 0 . # # x . . 1 # # x x . . 2 # x x x . . 3 . . . . . . 4 . . . . . .