BuildFun and SolveFun
Well, this took quite a while and I'm not entirely sure if the solver is cheating or not. While it has access to the entire maze all of the time, it only looks at the cell it's in, the walls surrounding it and, if there isn't a wall between them, the cells adjacent to it. If this is against the rules please let me know and I'll try to change it.
Anyway, here's the code:
#Architect function
def BuildFun(size,seed):
#Initialise grid and ensure inputs are valid
if size<15:size=15
if size>50:size=50
if seed<4:seed=4
if seed>size:seed=size
grid=[]
for x in range(size):
gridbuilder=[]
for y in range(size):gridbuilder.append([0,1,1])
grid.append(gridbuilder)
coords=[0,0]
grid[0][0][0]=1
#Generate maze
while 1:
#Choose a preffered direction based on location in grid and seed
pref=((((coords[0]+coords[1]+2)*int(size/2))%seed)+(seed%(abs(coords[0]-coords[1])+1)))%4
#Find legal moves
opt=[]
if coords[0]>0:opt+=[0] if grid[coords[0]-1][coords[1]][0]==0 else []
if coords[1]<size-1:opt+=[1] if grid[coords[0]][coords[1]+1][0]==0 else []
if coords[0]<size-1:opt+=[2] if grid[coords[0]+1][coords[1]][0]==0 else []
if coords[1]>0:opt+=[3] if grid[coords[0]][coords[1]-1][0]==0 else []
#There are legal moves
if len(opt)>0:
moved=False
while not moved:
#Try to move in preffered direction
if pref in opt:
if pref==0:
coords[0]-=1
grid[coords[0]][coords[1]][0]=1
grid[coords[0]][coords[1]][2]=0
elif pref==1:
grid[coords[0]][coords[1]][1]=0
coords[1]+=1
grid[coords[0]][coords[1]][0]=1
elif pref==2:
grid[coords[0]][coords[1]][2]=0
coords[0]+=1
grid[coords[0]][coords[1]][0]=1
else:
coords[1]-=1
grid[coords[0]][coords[1]][0]=1
grid[coords[0]][coords[1]][1]=0
moved=True
#Change preferred direction if unable to move
else:
pref+=1
if pref==4:pref=0
#There aren't legal moves
else:
moved=False
#Return to a previously visited location
if not moved:
try:
if grid[coords[0]-1][coords[1]][0]==1 and grid[coords[0]-1][coords[1]][2]==0:
grid[coords[0]][coords[1]][0]=2
coords[0]-=1
moved=True
except:pass
if not moved:
try:
if grid[coords[0]][coords[1]+1][0]==1 and grid[coords[0]][coords[1]][1]==0:
grid[coords[0]][coords[1]][0]=2
coords[1]+=1
moved=True
except:pass
if not moved:
try:
if grid[coords[0]+1][coords[1]][0]==1 and grid[coords[0]][coords[1]][2]==0:
grid[coords[0]][coords[1]][0]=2
coords[0]+=1
moved=True
except:pass
if not moved:
try:
if grid[coords[0]][coords[1]-1][0]==1 and grid[coords[0]][coords[1]-1][1]==0:
grid[coords[0]][coords[1]][0]=2
coords[1]-=1
moved=True
except:pass
#Check if finished
fin=True
for x in grid:
for y in x:
if y[0]==0:
fin=False
break
if not fin:break
if fin:break
for x in grid:
for y in x:
y[0]=0
#Find positions for start and finish such that the route between them is as long as possible
lsf=[[0,0],[0,0],0]
for y in range(size):
for x in range(size):
#Check all start positions
lengths=[]
coords=[[y,x,4,0]]
while len(coords)>0:
#Spread tracers out from start to the rest of the maze
for coord in coords:
opt=[]
if coord[0]>0:opt+=[0] if grid[coord[0]-1][coord[1]][2]==0 else []
opt+=[1] if grid[coord[0]][coord[1]][1]==0 else []
opt+=[2] if grid[coord[0]][coord[1]][2]==0 else []
if coord[1]>0:opt+=[3] if grid[coord[0]][coord[1]-1][1]==0 else []
try:opt.remove(coord[2])
except:pass
#Dead end, tracer dies and possible end point is recorded along with length
if len(opt)==0:
lengths.append([coord[0],coord[1],coord[3]])
coords.remove(coord)
else:
#Create more tracers at branch points
while len(opt)>1:
if opt[0]==0:coords.append([coord[0]-1,coord[1],2,coord[3]+1])
elif opt[0]==1:coords.append([coord[0],coord[1]+1,3,coord[3]+1])
elif opt[0]==2:coords.append([coord[0]+1,coord[1],0,coord[3]+1])
else:coords.append([coord[0],coord[1]-1,1,coord[3]+1])
del opt[0]
if opt[0]==0:
coord[0]-=1
coord[2]=2
coord[3]+=1
elif opt[0]==1:
coord[1]+=1
coord[2]=3
coord[3]+=1
elif opt[0]==2:
coord[0]+=1
coord[2]=0
coord[3]+=1
else:
coord[1]-=1
coord[2]=1
coord[3]+=1
#Find furthest distance and, if it's longer than the previous one, the start/end positions get updated
lengths=sorted(lengths,key=lambda x:x[2],reverse=True)
if lengths[0][2]>lsf[2]:lsf=[[y,x],[lengths[0][0],lengths[0][1]],lengths[0][2]]
#Find number of walls and output maze
w=draw(grid,size,lsf[0],lsf[1])
#Output maze information
print('Start = '+str(lsf[0]))
print('End = '+str(lsf[1]))
print('Distance = '+str(lsf[2]))
print('Walls = '+str(w))
print('Score = '+str(float(lsf[2])/float(w))[:5])
#Convert array grid to binary strings horizontal and vertical
horizontal=vertical=''
for y in range(size):
for x in range(size-1):vertical+=str(grid[y][x][1])
for y in range(size-1):
for x in range(size):horizontal+=str(grid[y][x][2])
#Save maze information to text file for use with SolveFun
save=open('Maze.txt','w')
save.write(str(size)+'\n'+str(lsf[0][0])+' '+str(lsf[0][1])+'\n'+str(lsf[1][0])+' '+str(lsf[1][1])+'\n'+horizontal+'\n'+vertical)
save.close()
#Solver function
def SolveFun():
try:
#Get maze information from text file
save=open('Maze.txt','r')
data=save.readlines()
save.close()
size=int(data[0])
s=data[1].rsplit(' ')
start=[int(s[0]),int(s[1])]
e=data[2].rsplit(' ')
end=[int(e[0]),int(e[1])]
horizontal=data[3].rstrip('\n')
vertical=data[4]
#Build maze from information
grid=[]
for y in range(size):
grid.append([])
for x in range(size):
grid[y].append([0,1,1])
for y in range(size):
for x in range(size-1):
grid[y][x][1]=int(vertical[y*(size-1)+x])
for y in range(size-1):
for x in range(size):
grid[y][x][2]=int(horizontal[y*size+x])
path=''
cpath=''
bs=0
pos=start[:]
grid[pos[0]][pos[1]][0]=1
while pos!=end:
#Want to move in direction of finish
if end[0]<pos[0] and pos[0]-end[0]>=abs(pos[1]-end[1]):pref=0
elif end[1]>pos[1] and end[1]-pos[1]>=abs(pos[0]-end[0]):pref=1
elif end[0]>pos[0] and end[0]-pos[0]>=abs(pos[1]-end[1]):pref=2
else:pref=3
#Find legal moves
opt=[]
if pos[0]>0:
if grid[pos[0]-1][pos[1]][2]==0:opt+=[0]if grid[pos[0]-1][pos[1]][0]==0 else[]
if pos[1]>0:
if grid[pos[0]][pos[1]-1][1]==0:opt+=[3]if grid[pos[0]][pos[1]-1][0]==0 else[]
if grid[pos[0]][pos[1]][2]==0:opt+=[2]if grid[pos[0]+1][pos[1]][0]==0 else[]
if grid[pos[0]][pos[1]][1]==0:opt+=[1]if grid[pos[0]][pos[1]+1][0]==0 else[]
if len(opt)>0:
moved=False
while not moved:
#Try to move in preferred direction
if pref in opt:
if pref==0:
pos[0]-=1
path+='0'
cpath+='0'
elif pref==1:
pos[1]+=1
path+='1'
cpath+='1'
elif pref==2:
pos[0]+=1
path+='2'
cpath+='2'
else:
pos[1]-=1
path+='3'
cpath+='3'
grid[pos[0]][pos[1]][0]=1
moved=True
#Change preferred direction by 1
else:
pref=(pref+1)%4
#No legal moves, backtrack
else:
bs+=1
grid[pos[0]][pos[1]][0]=2
if int(cpath[len(cpath)-1])==0:
pos[0]+=1
path+='2'
elif int(cpath[len(cpath)-1])==1:
pos[1]-=1
path+='3'
elif int(cpath[len(cpath)-1])==2:
pos[0]-=1
path+='0'
else:
pos[1]+=1
path+='1'
cpath=cpath[:len(cpath)-1]
#Output maze with solution as well as total steps and wasted steps
draw(grid,size,start,end)
print('\nPath taken:')
print(str(len(path))+' steps')
print(str(bs)+' backsteps')
print(str(bs*2)+' wasted steps')
except:print('Could not find maze')
def draw(grid,size,start,end):
#Build output in string d
d=' '
for x in range(size):d+=' '+str(x)[0]
d+='\n '
for x in range(size):d+=' ' if len(str(x))==1 else ' '+str(x)[1]
d+='\n '+'_'*(size*2-1)
w=0
for y in range(size):
d+='\n'+str(y)+' |' if len(str(y))==1 else '\n'+str(y)+' |'
for x in range(size):
if grid[y][x][2]:
if start==[y,x]:d+=UL.S+'S'+UL.E
elif end==[y,x]:d+=UL.S+'F'+UL.E
elif grid[y][x][0]==1:d+=UL.S+'*'+UL.E
else:d+='_'
w+=1
else:
if start==[y,x]:d+='S'
elif end==[y,x]:d+='F'
elif grid[y][x][0]==1:d+='*'
else:d+=' '
if grid[y][x][1]:
d+='|'
w+=1
else:d+=' '
#Output maze and return number of walls
print(d)
w-=size*2
return w
#Underlines text
class UL:
S = '\033[4m'
E = '\033[0m'
I realise that this is ridiculously long and not particularly easy to read, but I'm lazy so this is how it's staying.
BuildFun
The architect, BuildFun, is a fairly simple maze generating program that will always create a 'perfect' maze (one with no loops and where any two points will always have exactly one path between them). It bases its logic off of the seed input meaning that the mazes generated are pseudo-random with what often appear to be repeating patterns and, with the same seed and size, the same maze will be created.
Once the maze is generated, the program will attempt to maximise the maze's score by searching for the start point and end point that result in the longest path between them. To do this, it runs through every start point, spreads out tracers to find the end point furthest from it, and picks the combination with the longest path.
After this, it draws the maze, counts the walls and outputs the maze's information. This is the start point, end point, distance between them, number of walls and score. It also formats this information into the style described above for size, start and end, horizontal walls and vertical walls and saves this into a text file called Maze.txt for use later.
SolveFun
The solver, SolveFun, uses the text file Maze.txt as the input and works in a very similar way to the architect. For every move, it will choose a direction that it wants to go based on its relative position to the end and then it will look at the walls surrounding it. If a wall is not there, it will check to see if it has been in the cell adjacent to it and, if not, it will be added as possible option. It will then move in the direction closest to its preferred direction provided it has options. If it doesn't have options, it will backtrack until it does. This continues until it reaches the end.
As it moves, it records the path it is taking in the variable path which is used at the end to output the total number of steps. It also records the amount of times it had to backtrack used to calculate wasted steps at the end. When it reaches the end, it will output the maze with the shortest path from start to end marked on with *
s.
How to Run
Due to the method of outputting the maze (which includes underlining certain characters), this has to be run from a command line in the form
python -c 'import filename;filename.BuildFun(Size, Seed)'
and
python -c 'import filename;filename.SolveFun()'
where Size is an integer between 15 and 50 (inclusive) and Seed is an integer between 4 and Size (inclusive).