After watching Computerphile's video I decided to create my own maze solver and generator in Python. I've never written anything in Python so I'd like to get some feedback about my code, specifically about:
- code style
- project structure
- algorithms implementation
This is my first mini-project and as such I have no idea what is the correct way of doing things (e.g. arguments parsing baffles me a lot, I've no idea how to do it "properly").
mazr.py:
#!/usr/bin/env python3
import argparse
import generator
import solver
def parse_arguments():
parser = argparse.ArgumentParser()
# generator arguments
parser.add_argument("-g", "--generate", default=""
, help="filename used for generated maze")
parser.add_argument("-s", "--size", type=int, default=50
, help="size NxN of generated maze")
# solver arguments
parser.add_argument("-si", "--solve", default=""
, help="maze file to solve")
parser.add_argument("-sg", "--solvegenerated", action="store_true"
, default=False, help="generate and then solve a maze")
parser.add_argument("-dfs", "--dfs", action="store_true"
, default=False, help="solve using dfs algorithm")
parser.add_argument("-dji", "--djikstra", action="store_true"
, default=False
, help="solve using djikstra algorithm")
return parser.parse_args()
def main():
arguments = parse_arguments()
if not arguments.generate == "":
generator.create(arguments.generate, arguments.size)
if arguments.solvegenerated:
solver.solve(arguments.generate+".png", arguments.dfs,
arguments.djikstra)
if not arguments.solve == "":
solver.solve(arguments.solve, arguments.dfs, arguments.djikstra)
if __name__ == "__main__":
main()
generator.py:
#!/usr/bin/env python3
import random
import time
from PIL import Image
def generate_graph(size):
graph = [[] for i in range(size*size)]
graph_time = time.time()
verticies = 0
edges = 0
print("[*] generating graph")
posx = 1
for x in range(size):
posy = 1
for y in range(size):
verticies += 1
# graph[vertex's number] = [(it's real position), [connected
# vertex's number, (wall between verticies position)]]
# vertex's number
v = x*size+y
# append vertex's real position
graph[v].append((posx, posy))
if not x < 1:
graph[v].append([v-size, (posx-1, posy)])
edges += 1
if x+1 < size:
graph[v].append([v+size, (posx+1, posy)])
edges += 1
if not y < 1:
graph[v].append([v-1, (posx, posy-1)])
edges += 1
if y+1 < size:
graph[v].append([v+1, (posx, posy+1)])
edges += 1
# skip one pixel for wall
posy += 2
posx += 2
print(verticies, "verticies")
print(edges, "edges")
print("[#] finished in", time.time()-graph_time, "seconds")
return graph
def generate_maze(graph):
stack = []
path = []
visited = [False for i in range(len(graph))]
maze_time = time.time()
print("[*] generating maze")
# current vertex
v = random.randint(1, len(graph)) - 1
stack.append(v)
while stack:
visited[v] = True
path.append(graph[v][0])
valid = []
for i in range(1, len(graph[v])):
if not visited[graph[v][i][0]]:
valid.append(graph[v][i])
if valid:
choice = random.choice(valid)
path.append(choice[1])
stack.append(v)
v = choice[0]
else:
v = stack.pop()
print("[#] finished in", time.time()-maze_time, "seconds")
return path
def generate_image(filename, size, path):
print("[*] generating image")
image_time = time.time()
maze = Image.new('RGB', (size, size))
maze_matrix= maze.load()
for p in range(len(path)):
maze_matrix[path[p]] = (255, 255, 255)
# entrance and exit points
print("creating entry point at (1, 0)")
maze_matrix[(1, 0)] = (255, 255, 255)
print("creating exit point at", (size-1, size-2))
maze_matrix[(size-1, size-2)] = (255, 255, 255)
maze.save(filename)
print("[#] finished in", time.time()-image_time, "seconds")
def create(filename, size):
# correcting and setting up variables
filename += ".png"
size_real = (2 * size) + 1
creation_time = time.time()
print("[*] creating", filename)
print("size =", size_real, "x", size_real)
graph = generate_graph(size)
path = generate_maze(graph)
generate_image(filename, size_real, path)
print("[#] finished in", time.time()-creation_time, "seconds")
solver.py:
#!/usr/bin/env python3
import time
import os
from PIL import Image
import solve_dfs
import solve_dji
def save(filename, path, entrance, exit, algorithm):
solve = (204, 52, 53)
point = (57, 129, 237)
print("[#] generating image")
saving_time = time.time()
solved = Image.open(filename)
solved.mode = 'RGB'
solved_matrix = solved.load()
for i in range(len(path)):
solved_matrix[path[i]] = solve
solved_matrix[entrance] = point
solved_matrix[exit] = point
filename = os.path.splitext(os.path.basename(filename))[0]+algorithm+".png"
solved.save(filename)
print("saved", filename)
print("[#] finished in", time.time()-saving_time, "seconds")
def generate_graph(maze, width, height):
wall = (0, 0, 0)
verticies = 0
edges = 0
graph = [[] for i in range(width*height)]
print("[*] generating graph")
graph_time = time.time()
for x in range(width):
for y in range(height):
if not maze[x, y] == wall:
verticies += 1
# vertex's number
v = x*width+y
# append position
graph[v].append((x, y))
if not x < 1:
if not maze[x-1, y] == wall:
graph[v].append(v-width)
edges += 1
if x+1 < width:
if not maze[x+1, y] == wall:
graph[v].append(v+width)
edges += 1
if not y < 1:
if not maze[x, y-1] == wall:
graph[v].append(v-1)
edges += 1
if y+1 < height:
if not maze[x, y+1] == wall:
graph[v].append(v+1)
edges += 1
print(verticies, "verticies")
print(edges, "edges")
print("[#] finished in", time.time()-graph_time, "seconds")
return graph
def get_entrance_and_exit(maze, width, height):
wall = (0, 0, 0)
entrance = (0, 0)
exit = (0, 0)
print("[*] searching for entrance and exit")
entry_time = time.time()
for x in range(width):
if not maze[x, 0] == wall:
entrance = (x, 0)
break
for x in range(width):
if not maze[x, height-1] == wall:
exit = (x, height-1)
break
for y in range(height):
if not maze[0, y] == wall:
entrance = (0, y)
break
for y in range(height):
if not maze[width-1, y] == wall:
exit = (width-1, y)
break
print("found entrance at", entrance)
print("found exit at", exit)
print("[#] finished in", time.time()-entry_time, "seconds")
return entrance, exit
def solve(filename, dfs, dji):
print("[*] solving", filename)
solve_time = time.time()
print("opening image file")
try:
maze = Image.open(filename)
except:
print("unable to open file, quiting")
return
width, height = maze.size
print("size =", width, "x", height)
maze.mode = 'RGB'
maze_matrix = maze.load()
graph = generate_graph(maze_matrix, width, height)
entrance, exit = get_entrance_and_exit(maze_matrix, width, height)
if dfs:
path = solve_dfs.alg(graph, entrance[0]*width+entrance[1], exit)
save(filename, path, entrance, exit, "DFS")
if dji:
path = solve_dji.alg(graph, entrance[0]*width+entrance[1]
, exit[0]*width+ exit[1])
save(filename, path, entrance, exit, "DJIKSTRA")
print("[#] finished in", time.time()-solve_time, "seconds")
solve_dfs.py:
#!/usr/bin/env python3
import time
def alg(graph, entrance, exit):
visited = [False for i in range(len(graph))]
path = []
print("[*] solving using dfs")
dfs_time = time.time()
def dfs(v):
if graph[v][0] == exit:
return True
visited[v] = True
for i in range(1, len(graph[v])):
if not visited[graph[v][i]]:
if dfs(graph[v][i]):
path.append(graph[v][0])
return True
return False
try:
dfs(entrance)
except:
print("maze is simply to big for recursion, use other algorithm")
return []
print("solved in", len(path)+1, "steps") # +1 because it lacks exit
print("[#] finished in", time.time()-dfs_time, "seconds")
return path
solve_dji.py:
#!/usr/bin/env python3
import heapq
import time
def alg(graph, entrance, exit):
path = []
visited = [False for i in range(len(graph))]
distance = [9999999999 for i in range(len(graph))]
distance[entrance] = 0
previous = [0 for i in range(len(graph))]
print("[*] solving using djikstra")
djikstra_time = time.time()
# priority queue
pqueue = []
heapq.heappush(pqueue, (0, entrance))
while pqueue:
# distance and vertex
d, v = heapq.heappop(pqueue)
if not visited[v]:
for i in range(1, len(graph[v])):
if distance[graph[v][i]] > d + 1:
distance[graph[v][i]] = d + 1
heapq.heappush(pqueue, (d+1, graph[v][i]))
previous[graph[v][i]] = v
visited[v] = True
v = previous[exit]
while not v == entrance:
path.append(graph[v][0])
v = previous[v]
print("solved in", len(path)+2, "steps") # +2 because it lacks entrance and
# exit
print("[#] finished in", time.time() - djikstra_time, "seconds")
return path