## Tuesday, May 21, 2019

### A 3D Python Maze for Art of Illusion

I've always had a fascination with mazes. While messing with the Python plugin for Art of Illusion I decided to write a maze generator in Python. The image to the right is a rendering of the maze that is produced by that code. I did it as a Scripted Object, which was probably not the best idea since it recalculates the maze whenever something changes, but my excuse is that I was testing out some things related to Python scripted objects in Art of Illusion.

I'm including the code below, but with the caveat that it has a defect at one of the boundaries that I haven't figured out yet. It doesn't remove a wall for one or two of the cells. Aside from that, it will produce a good 3D maze.

import random
mazeWidth = 25
mazeLength = 25
wallLength = 1.2
wallHeight = 2.25
wallThickness = 0.05

WALL_UP = 0
WALL_DOWN = 1
WALL_FIXED = 2

class cell():
X = 0
Y = 0
N = WALL_UP
S = WALL_UP
E = WALL_UP
W = WALL_UP
visited = False

def createmaze(width, length):
m = []
for l in range(length):
row = []
for w in range(width):
_cell = cell()
_cell.X = w
_cell.Y = l
row.append(_cell)
m.append(row)

for l in range(length):
m[l].N = WALL_FIXED
m[l][width-1].S = WALL_FIXED

for w in range(width):
m[w].W = WALL_FIXED
m[length-1][w].E = WALL_FIXED

return m

def createWall(length, height, thickness):
wall = Cube(thickness, length, height)
return ObjectInfo(wall, CoordinateSystem(), "")

def drawMaze():
for i in range(mazeLength):
for j in range(mazeWidth):
if maze[i][j].W != WALL_DOWN:
obj = createWall(wallLength, wallHeight, wallThickness)
if not obj is None:
obj.getCoords().setOrientation(90.0, 0.0, 0.0)
obj.getCoords().setOrigin(Vec3(j*wallLength-(wallLength*0.5)-(mazeWidth*wallLength/2), wallHeight/2, i*wallLength-(mazeLength*wallLength/2)))
self.addObject(obj)

if maze[i][j].N != WALL_DOWN:
obj = createWall(wallLength, wallHeight, wallThickness)
if not obj is None:
obj.getCoords().setOrientation(90.0, 90.0, 0.0)
obj.getCoords().setOrigin(Vec3(j*wallLength-(mazeWidth*wallLength/2), wallHeight/2, i*wallLength-(wallLength*0.5)-(mazeLength*wallLength/2)))
self.addObject(obj)

for i in range(mazeLength):
obj = createWall(wallLength, wallHeight, wallThickness)
if not obj is None:
obj.getCoords().setOrientation(90.0, 0.0, 0.0)
obj.getCoords().setOrigin(Vec3(mazeWidth*wallLength-(wallLength*0.5)-(mazeWidth*wallLength/2), wallHeight/2, i*wallLength-(mazeLength*wallLength/2)))
self.addObject(obj)

for j in range(mazeWidth):
obj = createWall(wallLength, wallHeight, wallThickness)
if not obj is None:
obj.getCoords().setOrientation(90.0, 90.0, 0.0)
obj.getCoords().setOrigin(Vec3(j*wallLength-(mazeWidth*wallLength/2), wallHeight/2, mazeLength*wallLength-(wallLength*0.5)-(mazeLength*wallLength/2)))
self.addObject(obj)

def chooseUnvisited():
neighbors = []
if (current.N == WALL_UP) and (current.Y-1 >=0) and (not maze[current.Y-1][current.X].visited):
neighbors.append(maze[current.Y-1][current.X])
if (current.S == WALL_UP) and (current.Y+1 < mazeLength) and (not maze[current.Y+1][current.X].visited):
neighbors.append(maze[current.Y+1][current.X])
if (current.W == WALL_UP) and (current.X-1 >=0) and (not maze[current.Y][current.X-1].visited):
neighbors.append(maze[current.Y][current.X-1])
if (current.E == WALL_UP) and (current.X+1 < mazeWidth) and (not maze[current.Y][current.X+1].visited):
neighbors.append(maze[current.Y][current.X+1])

if len(neighbors) > 0:
return random.choice(neighbors)
else:
return current

def removeWall(cur, nxt):
print("Cur "+str(cur.X)+" "+str(cur.Y))
print("Nxt "+str(nxt.X)+" "+str(nxt.Y))
if nxt != cur:
if cur.Y < nxt.Y and cur.S != WALL_FIXED:
print("removeWall 1")
maze[cur.Y][cur.X].S = WALL_DOWN
maze[nxt.Y][nxt.X].N = WALL_DOWN
elif cur.Y > nxt.Y and cur.N != WALL_FIXED:
print("removeWall 2")
maze[cur.Y][cur.X].N = WALL_DOWN
maze[nxt.Y][nxt.X].S = WALL_DOWN
elif cur.X < nxt.X and cur.E != WALL_FIXED:
print("removeWall 3")
maze[cur.Y][cur.X].E = WALL_DOWN
maze[nxt.Y][nxt.X].W = WALL_DOWN
elif cur.X > nxt.X and cur.W != WALL_FIXED:
print("removeWall 4")
maze[cur.Y][cur.X].W = WALL_DOWN
maze[nxt.Y][nxt.X].E = WALL_DOWN
else:
print("removeWall 5")

maze = createmaze(mazeWidth, mazeLength)
theStack = []
current = maze # choose initial
theStack.append(current) # push current

# choose unvisited neighbor, pop if none found
next = chooseUnvisited()
next.visited = True
print("Next "+str(next.X)+" "+str(next.Y))
while len(theStack) > 0:
while next == current and len(theStack) > 0:
current = theStack.pop()
next = chooseUnvisited()
next.visited = True
print("Next "+str(next.X)+" "+str(next.Y))

removeWall(current, next)

current = next
next = chooseUnvisited()
next.visited = True
if next != current:
theStack.append(current)

drawMaze()