I made a Python program that creates a randomly generated maze using a dictionary called 'GRID'. It is a square grid containing coordinates and values of each tile. The tiles can be either walkable or empty.
import random as rnd
GRID={}#a square grid containing all tile coordinates of the maze and its corresponding values (empty or walkable)
RANGE=50#rnd.randint(150,300)#size of the grid's edges
e='O'#empty tile
w='X'#walkable tile
for x in range(RANGE):
for y in range(RANGE):
GRID.update({(x,y):e})
def writegrid(G,R,S):#writes the grid, its range and the start position into a text file because I can't deactivate python word wrap
grid=open('GRID.txt','w')
grid.write(str(S)+' '+str(R)+'\n')
for y in reversed(range(R)):
for x in range(R):
grid.write(G[x,y])
if x==R-1:
grid.write('\n')
else:
grid.write(' ')
def printgrid(G,R,S):#prints the grid, range and start position
print(S,R)
for y in reversed(range(R)):
for x in range(R):
print(G[x,y],end='')
if x==R-1:
print('')
else:
print(' ',end='')
The start of the maze has a certain shape I would like to keep, so I made a new dictionary called 'NEVER', so that the maze generation doesn't affect the tiles added to that dictionary. When generating the maze, the program determines whether some directions cannot be generated (e.g. intersects with an old path, or is about to hit 'NEVER'). If there are no directions available (e.g. a dead end), it migrates to different coordinates and restarts the direction searching there.
start_area_range=rnd.randint(4,6)#the size of the starting area
#the start area is an empty space with a linear path into the maze
#the further in the path, the smaller the area gets, like a pyramid with a strike in the middle
first_path=rnd.randint(0,3)#determines which edge will the start area be on
NEVER={}#never place a walkable tile there, it will ruin the shape of certain areas, such as start
#start set
if first_path==0:#left
START=(0,rnd.randint(start_area_range+1,RANGE-start_area_range-2))
elif first_path==1:#right
START=(RANGE-1,rnd.randint(start_area_range+1,RANGE-start_area_range-2))
elif first_path==2:#bottom
START=(rnd.randint(start_area_range+1,RANGE-start_area_range-2),0)
else:#top
START=(rnd.randint(start_area_range+1,RANGE-start_area_range-2),RANGE-1)
prev=tuple(START)
path=['U','D','R','L']#possible paths
f=0#number of walkable tiles
#start area set
if START[0]==0 or START[0]==RANGE-1:#left or right edge
for i in range(start_area_range):#moves position away from the start
for j in range(1-start_area_range+i,start_area_range-i):#shrinks the empty area perpendicular to the path
if j==0:#if it's on the path, make it walkable
GRID.update({(int(START[0]+i-i*2*(START[0]/(RANGE-1))),START[1]):w})
NEVER.update({(int(START[0]+i-i*2*(START[0]/(RANGE-1))),START[1]):w})
f+=1
else:#if it's not, make it empty
GRID.update({(int(START[0]+i-i*2*(START[0]//(RANGE-1))),START[1]+j):e})
NEVER.update({(int(START[0]+i-i*2*(START[0]//(RANGE-1))),START[1]+j):e})
else:#top or bottom edge
for i in range(start_area_range):
for j in range(1-start_area_range+i,start_area_range-i):
if j==0:#same as previous
GRID.update({(START[0],int(START[1]+i-i*2*(START[1]/(RANGE-1)))):w})
NEVER.update({(START[0],int(START[1]+i-i*2*(START[1]/(RANGE-1)))):w})
f+=1
else:#same as previous
GRID.update({(START[0]+j,int(START[1]+i-i*2*(START[0]/(RANGE-1)))):e})
NEVER.update({(START[0]+j,int(START[1]+i-i*2*(START[0]/(RANGE-1)))):e})
GRID.update({START:'S'})#so I know where to start
NEVER.update({START:'S'})
f+=1
DEAD=[]#list of dead ends
#maze set
while f!=int((RANGE**2)//2+rnd.randint(-10,10)):
curpath=list(path)#currently possible paths
x,y=tuple(prev)
#which tiles to check when placing the next walkable tile
u=[(x-1,y+1),(x,y+1),(x+1,y+1)]
d=[(x-1,y-1),(x,y-1),(x+1,y-1)]
r=[(x+1,y-1),(x+1,y),(x+1,y+1)]
l=[(x-1,y-1),(x-1,y),(x-1,y+1)]
#direction set
if y==RANGE-1:#if it's on the edge, it can't go further
curpath.remove('U')
else:#if it's not on the edge, check further
for i in u:
if i in NEVER or (i in GRID and GRID[i]==w):#if any of the checked tiles are walkable, stop checking
curpath.remove('U')
break
if y==0:
curpath.remove('D')
else:
for i in d:
if i in NEVER or (i in GRID and GRID[i]==w):
curpath.remove('D')
break
if x==RANGE-1:
curpath.remove('R')
else:
for i in r:
if i in NEVER or (i in GRID and GRID[i]==w):
curpath.remove('R')
break
if x==0:
curpath.remove('L')
else:
for i in l:
if i in NEVER or (i in GRID and GRID[i]==w):
curpath.remove('L')
break
#next tile set
if len(curpath)!=0:#if a walkable tile can be placed
DIR=rnd.randint(0,len(curpath)-1)
if curpath[DIR]=='U':
y+=1
elif curpath[DIR]=='D':
y-=1
elif curpath[DIR]=='R':
x+=1
else:
x-=1
GRID.update({(x,y):w})
f+=1
prev=tuple((x,y))
else:#if a walkable tile can't be placed,-
DEAD.append((x,y))#-add the position to the dead end list-
while True:
x,y=rnd.randint(0,RANGE-1),rnd.randint(0,RANGE-1)#-find a new position-
if (x,y) not in NEVER:
# and GRID[x,y]==w:#why doesn't it accept both conditions
prev=tuple((x,y))
break
The conditions should be that if the new coordinates are walkable, AND they are not in 'NEVER', migrate there. For some reason, using those conditions together creates an infinite loop.
Why are these conditions never true?