From 13ac5c5dcbe3d2e3046f8b56a0bc666c19ffd1c7 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 14:19:20 +0100 Subject: [PATCH 1/9] feat: save solved mazes --- main.py | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index bab67750..a5369207 100644 --- a/main.py +++ b/main.py @@ -23,14 +23,17 @@ Does not work if no path (Should print out NO PATH FOUND) import heapq import sys +import time +import os class MazeSolver: '''Maze Solver''' # self corresponds to "this" in js, it refers to object of MazeSolver class - def __init__(self, maze): + def __init__(self, maze, test_mode): # assign read maze 2D array to parameter from class MazeSolver + self.test = test_mode self.maze = maze self.start, self.end = self.find_start_and_end() @@ -84,7 +87,7 @@ class MazeSolver: # we use heapq so the element with lowest heuristic value will always # be at the top of heap heapq.heappush( - queue, (self.heuristic_euclidean( + queue, (self.heuristic_manhattan( self.start), self.start, [ self.start])) @@ -111,9 +114,10 @@ class MazeSolver: if neighbor not in visited: new_path = path + [neighbor] heapq.heappush( - queue, (self.heuristic_euclidean(neighbor), neighbor, new_path)) - print_maze(self.maze, new_path) - print() + queue, (self.heuristic_manhattan(neighbor), neighbor, new_path)) + if not self.test: + print_maze(self.maze, new_path) + print() return path # This heuristic returns the Manhattan distance between the given position @@ -158,17 +162,47 @@ def print_maze(maze, path=None): print(cell, end='') print() +def save_maze(maze, path=None, file_name = 'Maze', iteration = 0): + if not os.path.exists('solvedMazes'): + os.mkdir('solvedMazes') + f = open(f"solvedMazes/{iteration}solved{file_name}", "w") + if path is None: + path = [] + for row_i, row in enumerate(maze): + for col_i, cell in enumerate(row): + if (row_i, col_i) in path: + f.write('*') + else: + f.write(cell) + f.write('\n') + # Ran first in the code if __name__ == '__main__': - print(sys.argv) + start_time = time.perf_counter() + # print(sys.argv) file_name = 'maze.txt' + test_mode = False + folder_name = '' if len(sys.argv) > 1: file_name = sys.argv[1] + if sys.argv[1] == '-h' or sys.argv[1] == '--help': + print('python main.py - run the script against default maze file (any file named maze.txt in the code directory) \n python main.py filename.txt - run the script against filename.txt file \n python main.py -h --help print this prompt \n python main.py -t --test non interactive (does not print steps) for testing different heuristics, goes through entire folder of mazes file and compares heuristic speed and path length') + sys.exit() + if sys.argv[1] == '-t' or sys.argv[1] == '--test': + test_mode = True + file_name = 'maze.txt' + if len(sys.argv) > 2: + folder_name = sys.argv[2] # Open and load text file to array loadedMaze = load_maze(file_name) # Initialize MazeSolver object with maze as parameter - solver = MazeSolver(loadedMaze) + solver = MazeSolver(loadedMaze, test_mode) # Find path using MazeSolver solve method solvedPath = solver.solve() - print_maze(loadedMaze, solvedPath) + if not test_mode: + print_maze(loadedMaze, solvedPath) + save_maze(loadedMaze, solvedPath, file_name, 0) + end_time = time.perf_counter() + execution_time = end_time - start_time + print(f"The execution time is: {execution_time}") From be6d43bc0e694e96483f254fe4c6b1475c59f140 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 14:55:57 +0100 Subject: [PATCH 2/9] chore: battery dead --- main.py | 44 ++++++++++++++++++++- mazes/maze.txt | 10 +++++ mazes/maze1.txt | 10 +++++ {mazes => notWorking}/mazeDeadEnd.txt | 0 {mazes => notWorking}/mazeNoEnd.txt | 0 {mazes => notWorking}/mazeNoEndNoStart.txt | 0 {mazes => notWorking}/mazeNoPath.txt | 0 {mazes => notWorking}/mazeNoStart.txt | 0 {mazes => notWorking}/mazeNoStartNoPath.txt | 0 solvedMazes/0solvedmaze.txt | 10 +++++ solvedMazes/0solvedmaze1.txt | 10 +++++ 11 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 mazes/maze.txt create mode 100644 mazes/maze1.txt rename {mazes => notWorking}/mazeDeadEnd.txt (100%) rename {mazes => notWorking}/mazeNoEnd.txt (100%) rename {mazes => notWorking}/mazeNoEndNoStart.txt (100%) rename {mazes => notWorking}/mazeNoPath.txt (100%) rename {mazes => notWorking}/mazeNoStart.txt (100%) rename {mazes => notWorking}/mazeNoStartNoPath.txt (100%) create mode 100644 solvedMazes/0solvedmaze.txt create mode 100644 solvedMazes/0solvedmaze1.txt diff --git a/main.py b/main.py index a5369207..d362645a 100644 --- a/main.py +++ b/main.py @@ -25,6 +25,7 @@ import heapq import sys import time import os +from random import shuffle, randrange class MazeSolver: @@ -177,6 +178,35 @@ def save_maze(maze, path=None, file_name = 'Maze', iteration = 0): f.write('\n') + +def make_maze(w = 16, h = 8): + vis = [[0] * w + [1] for _ in range(h)] + [[1] * (w + 1)] + ver = [["# "] * w + ['#'] for _ in range(h)] + [[]] + hor = [["###"] * w + ['#'] for _ in range(h + 1)] + + def walk(x, y): + vis[y][x] = 1 + + d = [(x - 1, y), (x, y + 1), (x + 1, y), (x, y - 1)] + shuffle(d) + for (xx, yy) in d: + if vis[yy][xx]: continue + if xx == x: hor[max(y, yy)][x] = "# " + if yy == y: ver[y][max(x, xx)] = " " + walk(xx, yy) + + walk(randrange(w), randrange(h)) + + s = "" + for (a, b) in zip(hor, ver): + s += ''.join(a + ['\n'] + b + ['\n']) + sList = list(s) + sList[3*w + 3] = 'S' + sList[len(sList) - (3*w + 6)] = 'E' + s = ''.join(sList) + return s + + # Ran first in the code if __name__ == '__main__': start_time = time.perf_counter() @@ -187,7 +217,7 @@ if __name__ == '__main__': if len(sys.argv) > 1: file_name = sys.argv[1] if sys.argv[1] == '-h' or sys.argv[1] == '--help': - print('python main.py - run the script against default maze file (any file named maze.txt in the code directory) \n python main.py filename.txt - run the script against filename.txt file \n python main.py -h --help print this prompt \n python main.py -t --test non interactive (does not print steps) for testing different heuristics, goes through entire folder of mazes file and compares heuristic speed and path length') + print('python main.py - run the script against default maze file (any file named maze.txt in the code directory) \n python main.py filename.txt - run the script against filename.txt file \n python main.py -h --help print this prompt \n python main.py -t --test non interactive (does not print steps) for testing different heuristics, goes through entire folder of mazes file and compares heuristic speed and path length \n python main.py -g [NUMBER] - generates as many mazes as entered in Number parameter and puts it in the mazes folder') sys.exit() if sys.argv[1] == '-t' or sys.argv[1] == '--test': test_mode = True @@ -202,7 +232,19 @@ if __name__ == '__main__': solvedPath = solver.solve() if not test_mode: print_maze(loadedMaze, solvedPath) + if test_mode and folder_name != '': + for filename in os.listdir(folder_name): + filename_directory = os.path.join(folder_name, filename) + print(filename_directory) + # Open and load text file to array + loadedMaze = load_maze(filename_directory) + # Initialize MazeSolver object with maze as parameter + solver = MazeSolver(loadedMaze, test_mode) + # Find path using MazeSolver solve method + solvedPath = solver.solve() + save_maze(loadedMaze, solvedPath, filename, 0) save_maze(loadedMaze, solvedPath, file_name, 0) end_time = time.perf_counter() execution_time = end_time - start_time + print(make_maze()) print(f"The execution time is: {execution_time}") diff --git a/mazes/maze.txt b/mazes/maze.txt new file mode 100644 index 00000000..4ca4b64b --- /dev/null +++ b/mazes/maze.txt @@ -0,0 +1,10 @@ +########## +#S # +# #### # +# # # +# # ## # +# # # +##### ## +# # # +# #E # +########## diff --git a/mazes/maze1.txt b/mazes/maze1.txt new file mode 100644 index 00000000..4ca4b64b --- /dev/null +++ b/mazes/maze1.txt @@ -0,0 +1,10 @@ +########## +#S # +# #### # +# # # +# # ## # +# # # +##### ## +# # # +# #E # +########## diff --git a/mazes/mazeDeadEnd.txt b/notWorking/mazeDeadEnd.txt similarity index 100% rename from mazes/mazeDeadEnd.txt rename to notWorking/mazeDeadEnd.txt diff --git a/mazes/mazeNoEnd.txt b/notWorking/mazeNoEnd.txt similarity index 100% rename from mazes/mazeNoEnd.txt rename to notWorking/mazeNoEnd.txt diff --git a/mazes/mazeNoEndNoStart.txt b/notWorking/mazeNoEndNoStart.txt similarity index 100% rename from mazes/mazeNoEndNoStart.txt rename to notWorking/mazeNoEndNoStart.txt diff --git a/mazes/mazeNoPath.txt b/notWorking/mazeNoPath.txt similarity index 100% rename from mazes/mazeNoPath.txt rename to notWorking/mazeNoPath.txt diff --git a/mazes/mazeNoStart.txt b/notWorking/mazeNoStart.txt similarity index 100% rename from mazes/mazeNoStart.txt rename to notWorking/mazeNoStart.txt diff --git a/mazes/mazeNoStartNoPath.txt b/notWorking/mazeNoStartNoPath.txt similarity index 100% rename from mazes/mazeNoStartNoPath.txt rename to notWorking/mazeNoStartNoPath.txt diff --git a/solvedMazes/0solvedmaze.txt b/solvedMazes/0solvedmaze.txt new file mode 100644 index 00000000..648d7368 --- /dev/null +++ b/solvedMazes/0solvedmaze.txt @@ -0,0 +1,10 @@ +########## +#******* # +# ####* # +# # **# +# # ##*# +# #**# +##### *## +# # * # +# #* # +########## diff --git a/solvedMazes/0solvedmaze1.txt b/solvedMazes/0solvedmaze1.txt new file mode 100644 index 00000000..648d7368 --- /dev/null +++ b/solvedMazes/0solvedmaze1.txt @@ -0,0 +1,10 @@ +########## +#******* # +# ####* # +# # **# +# # ##*# +# #**# +##### *## +# # * # +# #* # +########## From b84ed11ee5d84ffc8496e22234bd6cd54fc75fac Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 15:47:07 +0100 Subject: [PATCH 3/9] chore: lint my beautiful code --- main.py | 211 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 118 insertions(+), 93 deletions(-) diff --git a/main.py b/main.py index d362645a..91e7fa90 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,10 @@ """ -Write a program that solves a maze using greedy best-first search algorithm. -The maze is a 2D grid with empty space, walls, a start, and an end position. -The objective is to find a path from start to end position. -The maze should be loaded from file. A step-by-step visualization of the +Write horizontal_line program that solves horizontal_line maze +using greedy best-first search algorithm. +The maze is horizontal_line 2D grid +with empty space, walls, horizontal_line start, and an end position. +The objective is to find horizontal_line path from start to end position. +The maze should be loaded from file. horizontal_line step-by-step visualization of the algorithm is required. It can be done in the console and an interface may be as simple as possible (but of course it does not have to). Example solution: https://angeluriot.com/maze_solver/. @@ -29,57 +31,58 @@ from random import shuffle, randrange class MazeSolver: - '''Maze Solver''' + """Maze Solver""" + # self corresponds to "this" in js, it refers to object of MazeSolver class - def __init__(self, maze, test_mode): + def __init__(self, maze, mode): # assign read maze 2D array to parameter from class MazeSolver - self.test = test_mode + self.test = mode self.maze = maze self.start, self.end = self.find_start_and_end() -# go through each character in 2D array and find one that corresponds to -# Start/End character + # go through each character in 2D array and find one that corresponds to + # Start/End character def find_start_and_end(self): - '''Finds start and end points in the maze''' + """Finds start and end points in the maze""" start = end = None for row_i, row in enumerate(self.maze): for col_i, cell in enumerate(row): - if cell == 'S': + if cell == "maze_data": start = (row_i, col_i) - elif cell == 'E': + elif cell == "E": end = (row_i, col_i) if start is not None and end is not None: return start, end print(f"DID NOT FOUND START OR END, Start: {start}, End: {end}") return start, end -# Go through each neighbor -# N -# N * N -# N -# If it is not a "wall" (#) add its position to list of neighbors + # Go through each neighbor + # N + # N * N + # N + # If it is not horizontal_line "wall" (#) add its position to list of neighbors def get_neighbors(self, position): - '''Finds point's neighbors''' + """Finds point'maze_data neighbors""" row, col = position neighbors = [] - if row > 0 and self.maze[row - 1][col] != '#': + if row > 0 and self.maze[row - 1][col] != "#": neighbors.append((row - 1, col)) - if col > 0 and self.maze[row][col - 1] != '#': + if col > 0 and self.maze[row][col - 1] != "#": neighbors.append((row, col - 1)) - if row < len(self.maze) - 1 and self.maze[row + 1][col] != '#': + if row < len(self.maze) - 1 and self.maze[row + 1][col] != "#": neighbors.append((row + 1, col)) - if col < len(self.maze[row]) - 1 and self.maze[row][col + 1] != '#': + if col < len(self.maze[row]) - 1 and self.maze[row][col + 1] != "#": neighbors.append((row, col + 1)) return neighbors -# find path through maze + # find path through maze def solve(self): - '''Solves the maze''' + """Solves the maze""" queue = [] # set means that values inside can not repeat visited = set() @@ -88,11 +91,10 @@ class MazeSolver: # we use heapq so the element with lowest heuristic value will always # be at the top of heap heapq.heappush( - queue, (self.heuristic_manhattan( - self.start), self.start, [ - self.start])) + queue, (self.heuristic_manhattan(self.start), self.start, [self.start]) + ) - # Go through queue until it's empty + # Go through queue until it'maze_data empty # Find neighbor (which is not wall) closest to the # END point (based on heuristic) # Go there and repeat @@ -115,135 +117,158 @@ class MazeSolver: if neighbor not in visited: new_path = path + [neighbor] heapq.heappush( - queue, (self.heuristic_manhattan(neighbor), neighbor, new_path)) + queue, (self.heuristic_manhattan(neighbor), neighbor, new_path) + ) if not self.test: print_maze(self.maze, new_path) print() return path # This heuristic returns the Manhattan distance between the given position - # and the maze's end + # and the maze'maze_data end def heuristic_manhattan(self, position): - '''Heuristic function that uses Manhattan distance''' + """Heuristic function that uses Manhattan distance""" return abs(position[0] - self.end[0]) + abs(position[1] - self.end[1]) # This heuristic returns the Euclidean distance between the given position - # and the maze's end + # and the maze'maze_data end def heuristic_euclidean(self, position): - '''Heuristic function that uses Euclidean distance''' - return (abs(position[0] - self.end[0])**2 + - abs(position[1] - self.end[1])**2)**0.5 + """Heuristic function that uses Euclidean distance""" + return ( + abs(position[0] - self.end[0]) ** 2 + abs(position[1] - self.end[1]) ** 2 + ) ** 0.5 # Open and load text file to array -def load_maze(filename): - '''Loads a maze from the specified file''' +def load_maze(maze_file_name): + """Loads horizontal_line maze from the specified file""" # Open for reading only and save to fileContents - with open(filename, 'r', encoding='utf8') as file_contents: + with open(maze_file_name, "r", encoding="utf8") as file_contents: # strip() removes extra white spaces from the beginning and the end of - # a string + # horizontal_line string # list() changes string to array of chars # Inside of square brackets we will have an array of characters for # each line of file - # After going through every line in a file we will have 2D array of arrays + # After going through every line in horizontal_line file we will have 2D array of arrays # of characters of every line maze = [list(line.strip()) for line in file_contents] return maze def print_maze(maze, path=None): - '''Prints the maze''' + """Prints the maze""" if path is None: path = [] for row_i, row in enumerate(maze): for col_i, cell in enumerate(row): if (row_i, col_i) in path: - print('*', end='') + print("*", end="") else: - print(cell, end='') + print(cell, end="") print() -def save_maze(maze, path=None, file_name = 'Maze', iteration = 0): - if not os.path.exists('solvedMazes'): - os.mkdir('solvedMazes') - f = open(f"solvedMazes/{iteration}solved{file_name}", "w") + +def save_maze(maze, path=None, saved_file="Maze", iteration=0): + """Saves maze from array to txt file""" + if not os.path.exists("solvedMazes"): + os.mkdir("solvedMazes") + with open(f"solvedMazes/{iteration}solved{saved_file}", "w", encoding="utf8") as file: + maze_file = file.read() if path is None: path = [] for row_i, row in enumerate(maze): for col_i, cell in enumerate(row): if (row_i, col_i) in path: - f.write('*') + maze_file.write("*") else: - f.write(cell) - f.write('\n') + maze_file.write(cell) + maze_file.write("\n") +def make_maze(width=16, height=8): + """ generate maze with given width and height """ + vis = [[0] * width + [1] for _ in range(height)] + [[1] * (width + 1)] + ver = [["# "] * width + ["#"] for _ in range(height)] + [[]] + hor = [["###"] * width + ["#"] for _ in range(height + 1)] -def make_maze(w = 16, h = 8): - vis = [[0] * w + [1] for _ in range(h)] + [[1] * (w + 1)] - ver = [["# "] * w + ['#'] for _ in range(h)] + [[]] - hor = [["###"] * w + ['#'] for _ in range(h + 1)] + def walk(x_coordinate, y_coordinate): + vis[y_coordinate][x_coordinate] = 1 - def walk(x, y): - vis[y][x] = 1 + neighbors = [(x_coordinate - 1, y_coordinate), + (x_coordinate, y_coordinate + 1), + (x_coordinate + 1, y_coordinate), + (x_coordinate, y_coordinate - 1)] + shuffle(neighbors) + for x_coordinate_neighbor, y_coordinate_neighbor in neighbors: + if vis[y_coordinate_neighbor][x_coordinate_neighbor]: + continue + if x_coordinate_neighbor == x_coordinate: + hor[max(y_coordinate, y_coordinate_neighbor)][x_coordinate] = "# " + if y_coordinate_neighbor == y_coordinate: + ver[y_coordinate][max(x_coordinate, x_coordinate_neighbor)] = " " + walk(x_coordinate_neighbor, y_coordinate_neighbor) - d = [(x - 1, y), (x, y + 1), (x + 1, y), (x, y - 1)] - shuffle(d) - for (xx, yy) in d: - if vis[yy][xx]: continue - if xx == x: hor[max(y, yy)][x] = "# " - if yy == y: ver[y][max(x, xx)] = " " - walk(xx, yy) + walk(randrange(width), randrange(height)) - walk(randrange(w), randrange(h)) - - s = "" - for (a, b) in zip(hor, ver): - s += ''.join(a + ['\n'] + b + ['\n']) - sList = list(s) - sList[3*w + 3] = 'S' - sList[len(sList) - (3*w + 6)] = 'E' - s = ''.join(sList) - return s + maze_data = "" + for horizontal_line, vertical_line in zip(hor, ver): + maze_data += "".join(horizontal_line + ["\n"] + vertical_line + ["\n"]) + maze_data_list = list(maze_data) + maze_data_list[3 * width + 3] = "maze_data" + maze_data_list[len(maze_data_list) - (3 * width + 6)] = "E" + maze_data = "".join(maze_data_list) + return maze_data # Ran first in the code -if __name__ == '__main__': +if __name__ == "__main__": start_time = time.perf_counter() # print(sys.argv) - file_name = 'maze.txt' - test_mode = False - folder_name = '' + FILE_NAME = "maze.txt" + TEST_MODE = False + FOLDER_NAME = "" if len(sys.argv) > 1: - file_name = sys.argv[1] - if sys.argv[1] == '-h' or sys.argv[1] == '--help': - print('python main.py - run the script against default maze file (any file named maze.txt in the code directory) \n python main.py filename.txt - run the script against filename.txt file \n python main.py -h --help print this prompt \n python main.py -t --test non interactive (does not print steps) for testing different heuristics, goes through entire folder of mazes file and compares heuristic speed and path length \n python main.py -g [NUMBER] - generates as many mazes as entered in Number parameter and puts it in the mazes folder') + FILE_NAME = sys.argv[1] + if sys.argv[1] == "-h" or sys.argv[1] == "--help": + print( +"""python main.py - run the script against default maze file +(any file named maze.txt in the code directory) + +python main.py filename.txt - run the script against filename.txt file +python main.py -h --help print this prompt +python main.py -t --test non interactive (does not print steps) for testing +different heuristics, goes through entire folder of mazes file and +compares heuristic speed and path length + +python main.py -g [NUMBER] - generates as many mazes as entered in +Number parameter and puts it in the mazes folder""" + ) sys.exit() - if sys.argv[1] == '-t' or sys.argv[1] == '--test': - test_mode = True - file_name = 'maze.txt' - if len(sys.argv) > 2: - folder_name = sys.argv[2] + if sys.argv[1] == "-t" or sys.argv[1] == "--test": + TEST_MODE = True + FILE_NAME = "maze.txt" + if len(sys.argv) > 2: + FOLDER_NAME = sys.argv[2] # Open and load text file to array - loadedMaze = load_maze(file_name) + loadedMaze = load_maze(FILE_NAME) # Initialize MazeSolver object with maze as parameter - solver = MazeSolver(loadedMaze, test_mode) + solver = MazeSolver(loadedMaze, TEST_MODE) # Find path using MazeSolver solve method solvedPath = solver.solve() - if not test_mode: + if not TEST_MODE: print_maze(loadedMaze, solvedPath) - if test_mode and folder_name != '': - for filename in os.listdir(folder_name): - filename_directory = os.path.join(folder_name, filename) + if TEST_MODE and FOLDER_NAME != "": + for filename in os.listdir(FOLDER_NAME): + filename_directory = os.path.join(FOLDER_NAME, filename) print(filename_directory) # Open and load text file to array loadedMaze = load_maze(filename_directory) # Initialize MazeSolver object with maze as parameter - solver = MazeSolver(loadedMaze, test_mode) + solver = MazeSolver(loadedMaze, TEST_MODE) # Find path using MazeSolver solve method solvedPath = solver.solve() save_maze(loadedMaze, solvedPath, filename, 0) - save_maze(loadedMaze, solvedPath, file_name, 0) + save_maze(loadedMaze, solvedPath, FILE_NAME, 0) end_time = time.perf_counter() execution_time = end_time - start_time print(make_maze()) From 95fcae04d81441721c5e3874c4b4932c7e1f2075 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 17:30:25 +0100 Subject: [PATCH 4/9] feat: maze generation --- .pylintrc | 3 + main.py | 153 ++++++++++++++++++------------- mazes/maze.txt | 10 -- mazes/maze1.txt | 10 -- notWorking/mazeDeadEnd.txt | 5 - notWorking/mazeNoEnd.txt | 32 ------- notWorking/mazeNoEndNoStart.txt | 32 ------- notWorking/mazeNoPath.txt | 32 ------- notWorking/mazeNoStart.txt | 32 ------- notWorking/mazeNoStartNoPath.txt | 32 ------- solvedMazes/0solvedmaze.txt | 10 -- solvedMazes/0solvedmaze1.txt | 10 -- 12 files changed, 91 insertions(+), 270 deletions(-) create mode 100644 .pylintrc delete mode 100644 mazes/maze.txt delete mode 100644 mazes/maze1.txt delete mode 100644 notWorking/mazeDeadEnd.txt delete mode 100644 notWorking/mazeNoEnd.txt delete mode 100644 notWorking/mazeNoEndNoStart.txt delete mode 100644 notWorking/mazeNoPath.txt delete mode 100644 notWorking/mazeNoStart.txt delete mode 100644 notWorking/mazeNoStartNoPath.txt delete mode 100644 solvedMazes/0solvedmaze.txt delete mode 100644 solvedMazes/0solvedmaze1.txt diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..4501ff29 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,3 @@ +[DESIGN] +# Maximum number of statements in function / method body +max-statements=16 \ No newline at end of file diff --git a/main.py b/main.py index 91e7fa90..a5c306c1 100644 --- a/main.py +++ b/main.py @@ -50,7 +50,7 @@ class MazeSolver: for row_i, row in enumerate(self.maze): for col_i, cell in enumerate(row): - if cell == "maze_data": + if cell == "S": start = (row_i, col_i) elif cell == "E": end = (row_i, col_i) @@ -81,26 +81,8 @@ class MazeSolver: # find path through maze - def solve(self): - """Solves the maze""" - queue = [] - # set means that values inside can not repeat - visited = set() - # https://docs.python.org/3/library/heapq.html - # push onto the queue (which becomes heapq), element containing values - # we use heapq so the element with lowest heuristic value will always - # be at the top of heap - heapq.heappush( - queue, (self.heuristic_manhattan(self.start), self.start, [self.start]) - ) - - # Go through queue until it'maze_data empty - # Find neighbor (which is not wall) closest to the - # END point (based on heuristic) - # Go there and repeat - # if cannot find path it starts over but skips the path that lead it to - # dead end - path = None + def solve_loop(self, queue, visited): + """ Goes through maze and finds the path """ while queue: # pop first element of heap # first value is skipped and we only save current position and path @@ -124,6 +106,27 @@ class MazeSolver: print() return path + def solve(self): + """Solves the maze""" + queue = [] + # set means that values inside can not repeat + visited = set() + # https://docs.python.org/3/library/heapq.html + # push onto the queue (which becomes heapq), element containing values + # we use heapq so the element with lowest heuristic value will always + # be at the top of heap + heapq.heappush( + queue, (self.heuristic_manhattan(self.start), self.start, [self.start]) + ) + + # Go through queue until it'maze_data empty + # Find neighbor (which is not wall) closest to the + # END point (based on heuristic) + # Go there and repeat + # if cannot find path it starts over but skips the path that lead it to + # dead end + return self.solve_loop(queue, visited) + # This heuristic returns the Manhattan distance between the given position # and the maze'maze_data end def heuristic_manhattan(self, position): @@ -167,23 +170,27 @@ def print_maze(maze, path=None): print(cell, end="") print() - -def save_maze(maze, path=None, saved_file="Maze", iteration=0): +def save_maze(maze, solved=True, path=None, saved_file="Maze", iteration=0): """Saves maze from array to txt file""" - if not os.path.exists("solvedMazes"): - os.mkdir("solvedMazes") - with open(f"solvedMazes/{iteration}solved{saved_file}", "w", encoding="utf8") as file: - maze_file = file.read() - if path is None: - path = [] - for row_i, row in enumerate(maze): - for col_i, cell in enumerate(row): - if (row_i, col_i) in path: - maze_file.write("*") - else: - maze_file.write(cell) - maze_file.write("\n") - + if solved: + folder_name = "solvedMazes" + else: + folder_name = "generatedMazes" + if not os.path.exists(folder_name): + os.mkdir(folder_name) + with open(f"{folder_name}/{iteration}{saved_file}", "w", encoding="utf8") as maze_file: + if path is None: + path = [] + for row_i, row in enumerate(maze): + for col_i, cell in enumerate(row): + if (row_i, col_i) in path: + maze_file.write("*") + else: + maze_file.write(cell) + if solved: + maze_file.write("\n") + if not solved: + maze_file.write("\n") def make_maze(width=16, height=8): """ generate maze with given width and height """ @@ -214,23 +221,14 @@ def make_maze(width=16, height=8): for horizontal_line, vertical_line in zip(hor, ver): maze_data += "".join(horizontal_line + ["\n"] + vertical_line + ["\n"]) maze_data_list = list(maze_data) - maze_data_list[3 * width + 3] = "maze_data" + maze_data_list[3 * width + 3] = "S" maze_data_list[len(maze_data_list) - (3 * width + 6)] = "E" maze_data = "".join(maze_data_list) return maze_data - -# Ran first in the code -if __name__ == "__main__": - start_time = time.perf_counter() - # print(sys.argv) - FILE_NAME = "maze.txt" - TEST_MODE = False - FOLDER_NAME = "" - if len(sys.argv) > 1: - FILE_NAME = sys.argv[1] - if sys.argv[1] == "-h" or sys.argv[1] == "--help": - print( +def print_help(): + """prints help""" + print( """python main.py - run the script against default maze file (any file named maze.txt in the code directory) @@ -240,36 +238,61 @@ python main.py -t --test non interactive (does not print steps) for testing different heuristics, goes through entire folder of mazes file and compares heuristic speed and path length -python main.py -g [NUMBER] - generates as many mazes as entered in +python main.py -g --generate [NUMBER] - generates as many mazes as entered in Number parameter and puts it in the mazes folder""" ) + +def test_mode(): + """ Loads and solves multiple mazes in order to compare heuristics """ + for filename in os.listdir(FOLDER_NAME): + filename_directory = os.path.join(FOLDER_NAME, filename) + print(filename_directory) + # Open and load text file to array + loaded_maze_test = load_maze(filename_directory) + # Initialize MazeSolver object with maze as parameter + solver_test = MazeSolver(loaded_maze_test, TEST_MODE) + # Find path using MazeSolver solve method + solved_path = solver_test.solve() + save_maze(loadedMaze, True, solved_path, filename, 0) + + +# Ran first in the code +if __name__ == "__main__": + start_time = time.perf_counter() + # print(sys.argv) + FILE_NAME = "maze.txt" + TEST_MODE = False + FOLDER_NAME = "" + GENERATE_AMOUNT = 0 + if len(sys.argv) > 1: + FILE_NAME = sys.argv[1] + if sys.argv[1] == "-h" or sys.argv[1] == "--help": + print_help() sys.exit() if sys.argv[1] == "-t" or sys.argv[1] == "--test": TEST_MODE = True FILE_NAME = "maze.txt" if len(sys.argv) > 2: FOLDER_NAME = sys.argv[2] + if sys.argv[1] == '-g' or sys.argv[1] == '--generate': + if len(sys.argv) > 2: + GENERATE_AMOUNT = int(sys.argv[2]) + if GENERATE_AMOUNT > 0: + for n in range(GENERATE_AMOUNT): + GENERATED_MAZE = make_maze() + save_maze(GENERATED_MAZE, False, None, f'generated{n}.txt') + sys.exit() # Open and load text file to array loadedMaze = load_maze(FILE_NAME) # Initialize MazeSolver object with maze as parameter solver = MazeSolver(loadedMaze, TEST_MODE) # Find path using MazeSolver solve method - solvedPath = solver.solve() + SOLVED_PATH = solver.solve() if not TEST_MODE: - print_maze(loadedMaze, solvedPath) + print_maze(loadedMaze, SOLVED_PATH) if TEST_MODE and FOLDER_NAME != "": - for filename in os.listdir(FOLDER_NAME): - filename_directory = os.path.join(FOLDER_NAME, filename) - print(filename_directory) - # Open and load text file to array - loadedMaze = load_maze(filename_directory) - # Initialize MazeSolver object with maze as parameter - solver = MazeSolver(loadedMaze, TEST_MODE) - # Find path using MazeSolver solve method - solvedPath = solver.solve() - save_maze(loadedMaze, solvedPath, filename, 0) - save_maze(loadedMaze, solvedPath, FILE_NAME, 0) + test_mode() + save_maze(loadedMaze, True, SOLVED_PATH, FILE_NAME, 0) end_time = time.perf_counter() execution_time = end_time - start_time - print(make_maze()) print(f"The execution time is: {execution_time}") diff --git a/mazes/maze.txt b/mazes/maze.txt deleted file mode 100644 index 4ca4b64b..00000000 --- a/mazes/maze.txt +++ /dev/null @@ -1,10 +0,0 @@ -########## -#S # -# #### # -# # # -# # ## # -# # # -##### ## -# # # -# #E # -########## diff --git a/mazes/maze1.txt b/mazes/maze1.txt deleted file mode 100644 index 4ca4b64b..00000000 --- a/mazes/maze1.txt +++ /dev/null @@ -1,10 +0,0 @@ -########## -#S # -# #### # -# # # -# # ## # -# # # -##### ## -# # # -# #E # -########## diff --git a/notWorking/mazeDeadEnd.txt b/notWorking/mazeDeadEnd.txt deleted file mode 100644 index 4fbe61e9..00000000 --- a/notWorking/mazeDeadEnd.txt +++ /dev/null @@ -1,5 +0,0 @@ -################# -# # -# ####### # -#S # E# -################# \ No newline at end of file diff --git a/notWorking/mazeNoEnd.txt b/notWorking/mazeNoEnd.txt deleted file mode 100644 index acf1cf03..00000000 --- a/notWorking/mazeNoEnd.txt +++ /dev/null @@ -1,32 +0,0 @@ -################################ -# S # # # -# ######### #### ## # # -# # # # # -# # ######### ####### # -# # # # -# # #### # ####### ####### -# # # # # # -# ####### ########### # # -# # ##### # -#### ######### # # # # - # # # #### # -#### ########### # # -# # ##### ##### -# ######### # # # -# ###### ##### -# ####### ####### # -# # # # ##### -# # ######### # # # -# # # # ###### -# ####### ######### # -# # # -# ######### ############ # -# # # # -# ######### #### ####### # -# # # # # -# # ####### # ######### # -# # # # # # -# # # #### ######### #### -# # # # # -# ######### ###### ##### # -################################ diff --git a/notWorking/mazeNoEndNoStart.txt b/notWorking/mazeNoEndNoStart.txt deleted file mode 100644 index ffdd9a08..00000000 --- a/notWorking/mazeNoEndNoStart.txt +++ /dev/null @@ -1,32 +0,0 @@ -################################ -# # # # -# ######### #### ## # # -# # # # # -# # ######### ####### # -# # # # -# # #### # ####### ####### -# # # # # # -# ####### ########### # # -# # ##### # -#### ######### # # # # - # # # #### # -#### ########### # # -# # ##### ##### -# ######### # # # -# ###### ##### -# ####### ####### # -# # # # ##### -# # ######### # # # -# # # # ###### -# ####### ######### # -# # # -# ######### ############ # -# # # # -# ######### #### ####### # -# # # # # -# # ####### # ######### # -# # # # # # -# # # #### ######### #### -# # # # # -# ######### ###### ##### # -################################ diff --git a/notWorking/mazeNoPath.txt b/notWorking/mazeNoPath.txt deleted file mode 100644 index 08c5f55e..00000000 --- a/notWorking/mazeNoPath.txt +++ /dev/null @@ -1,32 +0,0 @@ -################################ -# # # # -# ######### #### ## # # -# # # # # -# # ######### ####### # -# # # # -# # #### # ####### ####### -# # # # # # -# ####### ########### # # -# # ##### # -#### ######### # # # # - # # # #### # -#### ########### # # -# # ##### ##### -# ######### # # # -# ###### ##### -# ####### ####### # -# # # # ##### -# # ######### # # # -# # # # ###### -# ####### ######### # -# # # -# ######### ############ # -# # # # -# ######### #### ####### # -# # # # # -# # ####### # ######### # -# # # # # # -# # # #### ######### #### -# # # # # # -# ######### ###### #####E# -################################ diff --git a/notWorking/mazeNoStart.txt b/notWorking/mazeNoStart.txt deleted file mode 100644 index a4cebbd7..00000000 --- a/notWorking/mazeNoStart.txt +++ /dev/null @@ -1,32 +0,0 @@ -################################ -# # # # -# ######### #### ## # # -# # # # # -# # ######### ####### # -# # # # -# # #### # ####### ####### -# # # # # # -# ####### ########### # # -# # ##### # -#### ######### # # # # - # # # #### # -#### ########### # # -# # ##### ##### -# ######### # # # -# ###### ##### -# ####### ####### # -# # # # ##### -# # ######### # # # -# # # # ###### -# ####### ######### # -# # # -# ######### ############ # -# # # # -# ######### #### ####### # -# # # # # -# # ####### # ######### # -# # # # # # -# # # #### ######### #### -# # # # # -# ######### ###### #####E# -################################ diff --git a/notWorking/mazeNoStartNoPath.txt b/notWorking/mazeNoStartNoPath.txt deleted file mode 100644 index 3ea422ad..00000000 --- a/notWorking/mazeNoStartNoPath.txt +++ /dev/null @@ -1,32 +0,0 @@ -################################ -# S # # # -# ######### #### ## # # -# # # # # -# # ######### ####### # -# # # # -# # #### # ####### ####### -# # # # # # -# ####### ########### # # -# # ##### # -#### ######### # # # # - # # # #### # -#### ########### # # -# # ##### ##### -# ######### # # # -# ###### ##### -# ####### ####### # -# # # # ##### -# # ######### # # # -# # # # ###### -# ####### ######### # -# # # -# ######### ############ # -# # # # -# ######### #### ####### # -# # # # # -# # ####### # ######### # -# # # # # # -# # # #### ######### #### -# # # # # # -# ######### ###### #####E# -################################ diff --git a/solvedMazes/0solvedmaze.txt b/solvedMazes/0solvedmaze.txt deleted file mode 100644 index 648d7368..00000000 --- a/solvedMazes/0solvedmaze.txt +++ /dev/null @@ -1,10 +0,0 @@ -########## -#******* # -# ####* # -# # **# -# # ##*# -# #**# -##### *## -# # * # -# #* # -########## diff --git a/solvedMazes/0solvedmaze1.txt b/solvedMazes/0solvedmaze1.txt deleted file mode 100644 index 648d7368..00000000 --- a/solvedMazes/0solvedmaze1.txt +++ /dev/null @@ -1,10 +0,0 @@ -########## -#******* # -# ####* # -# # **# -# # ##*# -# #**# -##### *## -# # * # -# #* # -########## From d1f1479000551ccb804032560599129934faecb8 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 17:42:06 +0100 Subject: [PATCH 5/9] chore: shorten function length to 16 --- main.py | 79 +++++++++++++++++++++++++------------------ solvedMazes/0maze.txt | 10 ++++++ 2 files changed, 57 insertions(+), 32 deletions(-) create mode 100644 solvedMazes/0maze.txt diff --git a/main.py b/main.py index a5c306c1..1090a180 100644 --- a/main.py +++ b/main.py @@ -170,14 +170,20 @@ def print_maze(maze, path=None): print(cell, end="") print() -def save_maze(maze, solved=True, path=None, saved_file="Maze", iteration=0): - """Saves maze from array to txt file""" + +def create_maze_folder(solved): + """ Creates folder for generated or solved mazes""" if solved: folder_name = "solvedMazes" else: folder_name = "generatedMazes" if not os.path.exists(folder_name): os.mkdir(folder_name) + return folder_name + +def save_maze(maze, solved=True, path=None, saved_file="Maze", iteration=0): + """Saves maze from array to txt file""" + folder_name = create_maze_folder(solved) with open(f"{folder_name}/{iteration}{saved_file}", "w", encoding="utf8") as maze_file: if path is None: path = [] @@ -192,6 +198,17 @@ def save_maze(maze, solved=True, path=None, saved_file="Maze", iteration=0): if not solved: maze_file.write("\n") +def fill_generated_maze(hor, ver, width): + """ Fills generated maze array from horizontal and vertical lines """ + maze_data = "" + for horizontal_line, vertical_line in zip(hor, ver): + maze_data += "".join(horizontal_line + ["\n"] + vertical_line + ["\n"]) + maze_data_list = list(maze_data) + maze_data_list[3 * width + 3] = "S" + maze_data_list[len(maze_data_list) - (3 * width + 6)] = "E" + maze_data = "".join(maze_data_list) + return maze_data + def make_maze(width=16, height=8): """ generate maze with given width and height """ vis = [[0] * width + [1] for _ in range(height)] + [[1] * (width + 1)] @@ -217,14 +234,7 @@ def make_maze(width=16, height=8): walk(randrange(width), randrange(height)) - maze_data = "" - for horizontal_line, vertical_line in zip(hor, ver): - maze_data += "".join(horizontal_line + ["\n"] + vertical_line + ["\n"]) - maze_data_list = list(maze_data) - maze_data_list[3 * width + 3] = "S" - maze_data_list[len(maze_data_list) - (3 * width + 6)] = "E" - maze_data = "".join(maze_data_list) - return maze_data + return fill_generated_maze(hor, ver, width) def print_help(): """prints help""" @@ -235,11 +245,14 @@ def print_help(): python main.py filename.txt - run the script against filename.txt file python main.py -h --help print this prompt python main.py -t --test non interactive (does not print steps) for testing -different heuristics, goes through entire folder of mazes file and +different heuristics, goes through entire generatedMazes folder and +compares heuristic speed and path length +python main.py -t --test [FOLDER] non interactive (does not print steps) for testing +different heuristics, goes through entire [FOLDER] folder and compares heuristic speed and path length python main.py -g --generate [NUMBER] - generates as many mazes as entered in -Number parameter and puts it in the mazes folder""" +Number parameter and puts it in the generatedMazes folder""" ) def test_mode(): @@ -248,13 +261,23 @@ def test_mode(): filename_directory = os.path.join(FOLDER_NAME, filename) print(filename_directory) # Open and load text file to array - loaded_maze_test = load_maze(filename_directory) + loaded_maze = load_maze(filename_directory) # Initialize MazeSolver object with maze as parameter - solver_test = MazeSolver(loaded_maze_test, TEST_MODE) + solver_test = MazeSolver(loaded_maze, TEST_MODE) # Find path using MazeSolver solve method solved_path = solver_test.solve() - save_maze(loadedMaze, True, solved_path, filename, 0) + save_maze(loaded_maze, True, solved_path, filename, 0) +def default(): + """ Runs default operation - reads, solves and prints single maze from file """ + # Open and load text file to array + loaded_maze = load_maze(FILE_NAME) + # Initialize MazeSolver object with maze as parameter + solver = MazeSolver(loaded_maze, TEST_MODE) + # Find path using MazeSolver solve method + solved_path = solver.solve() + print_maze(loaded_maze, solved_path) + save_maze(loaded_maze, True, solved_path, FILE_NAME, 0) # Ran first in the code if __name__ == "__main__": @@ -265,34 +288,26 @@ if __name__ == "__main__": FOLDER_NAME = "" GENERATE_AMOUNT = 0 if len(sys.argv) > 1: - FILE_NAME = sys.argv[1] if sys.argv[1] == "-h" or sys.argv[1] == "--help": print_help() sys.exit() if sys.argv[1] == "-t" or sys.argv[1] == "--test": TEST_MODE = True FILE_NAME = "maze.txt" + FOLDER_NAME = "generatedMazes" if len(sys.argv) > 2: FOLDER_NAME = sys.argv[2] + test_mode() + sys.exit() if sys.argv[1] == '-g' or sys.argv[1] == '--generate': if len(sys.argv) > 2: GENERATE_AMOUNT = int(sys.argv[2]) - if GENERATE_AMOUNT > 0: - for n in range(GENERATE_AMOUNT): - GENERATED_MAZE = make_maze() - save_maze(GENERATED_MAZE, False, None, f'generated{n}.txt') - sys.exit() - # Open and load text file to array - loadedMaze = load_maze(FILE_NAME) - # Initialize MazeSolver object with maze as parameter - solver = MazeSolver(loadedMaze, TEST_MODE) - # Find path using MazeSolver solve method - SOLVED_PATH = solver.solve() - if not TEST_MODE: - print_maze(loadedMaze, SOLVED_PATH) - if TEST_MODE and FOLDER_NAME != "": - test_mode() - save_maze(loadedMaze, True, SOLVED_PATH, FILE_NAME, 0) + for n in range(GENERATE_AMOUNT): + GENERATED_MAZE = make_maze() + save_maze(GENERATED_MAZE, False, None, f'generated{n}.txt') + sys.exit() + FILE_NAME = sys.argv[1] + default() end_time = time.perf_counter() execution_time = end_time - start_time print(f"The execution time is: {execution_time}") diff --git a/solvedMazes/0maze.txt b/solvedMazes/0maze.txt new file mode 100644 index 00000000..648d7368 --- /dev/null +++ b/solvedMazes/0maze.txt @@ -0,0 +1,10 @@ +########## +#******* # +# ####* # +# # **# +# # ##*# +# #**# +##### *## +# # * # +# #* # +########## From 233a2e3dbd80c0d79b6e785b1f785dbc81c98360 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 18:10:19 +0100 Subject: [PATCH 6/9] feat: add random heuristic --- main.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index 1090a180..f3561ea4 100644 --- a/main.py +++ b/main.py @@ -27,7 +27,7 @@ import heapq import sys import time import os -from random import shuffle, randrange +from random import shuffle, randrange, random class MazeSolver: @@ -99,7 +99,7 @@ class MazeSolver: if neighbor not in visited: new_path = path + [neighbor] heapq.heappush( - queue, (self.heuristic_manhattan(neighbor), neighbor, new_path) + queue, (self.heuristic_random(neighbor), neighbor, new_path) ) if not self.test: print_maze(self.maze, new_path) @@ -116,7 +116,7 @@ class MazeSolver: # we use heapq so the element with lowest heuristic value will always # be at the top of heap heapq.heappush( - queue, (self.heuristic_manhattan(self.start), self.start, [self.start]) + queue, (self.heuristic_random(self.start), self.start, [self.start]) ) # Go through queue until it'maze_data empty @@ -141,6 +141,10 @@ class MazeSolver: abs(position[0] - self.end[0]) ** 2 + abs(position[1] - self.end[1]) ** 2 ) ** 0.5 + def heuristic_random(self, position): + """Heuristic function that just returns random value between 0 and 1""" + return random() + # Open and load text file to array def load_maze(maze_file_name): @@ -257,16 +261,27 @@ Number parameter and puts it in the generatedMazes folder""" def test_mode(): """ Loads and solves multiple mazes in order to compare heuristics """ + create_maze_folder(False) + sum_of_paths = 0 + files_amount = 0 + sum_of_time = 0 for filename in os.listdir(FOLDER_NAME): filename_directory = os.path.join(FOLDER_NAME, filename) - print(filename_directory) # Open and load text file to array loaded_maze = load_maze(filename_directory) # Initialize MazeSolver object with maze as parameter solver_test = MazeSolver(loaded_maze, TEST_MODE) # Find path using MazeSolver solve method + start_time = time.perf_counter() solved_path = solver_test.solve() + end_time = time.perf_counter() + sum_of_time += end_time - start_time + sum_of_paths += len(solved_path) save_maze(loaded_maze, True, solved_path, filename, 0) + files_amount += 1 + average_path = sum_of_paths / files_amount + average_time = sum_of_time / files_amount + print(f"For: {files_amount} files, sum of path lengths = {sum_of_paths}, average path length = {average_path}, sum_of_time = {sum_of_time}, average time to solve: {average_time}") def default(): """ Runs default operation - reads, solves and prints single maze from file """ @@ -281,7 +296,6 @@ def default(): # Ran first in the code if __name__ == "__main__": - start_time = time.perf_counter() # print(sys.argv) FILE_NAME = "maze.txt" TEST_MODE = False @@ -305,9 +319,6 @@ if __name__ == "__main__": for n in range(GENERATE_AMOUNT): GENERATED_MAZE = make_maze() save_maze(GENERATED_MAZE, False, None, f'generated{n}.txt') - sys.exit() + sys.exit() FILE_NAME = sys.argv[1] default() - end_time = time.perf_counter() - execution_time = end_time - start_time - print(f"The execution time is: {execution_time}") From 3906b877d9d3365d0e00d23d389154edf49fe1f1 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 19:41:33 +0100 Subject: [PATCH 7/9] feat: heuristics tests --- main.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index f3561ea4..038f426f 100644 --- a/main.py +++ b/main.py @@ -83,6 +83,8 @@ class MazeSolver: def solve_loop(self, queue, visited): """ Goes through maze and finds the path """ + heuristic_total_time = 0 + heuristics_called = 0 while queue: # pop first element of heap # first value is skipped and we only save current position and path @@ -98,13 +100,16 @@ class MazeSolver: for neighbor in self.get_neighbors(current): if neighbor not in visited: new_path = path + [neighbor] + heuristic, heuristic_time = self.heuristic_manhattan(neighbor) + heuristic_total_time += heuristic_time + heuristics_called += 1 heapq.heappush( - queue, (self.heuristic_random(neighbor), neighbor, new_path) + queue, (heuristic, neighbor, new_path) ) if not self.test: print_maze(self.maze, new_path) print() - return path + return path, heuristic_total_time, heuristics_called def solve(self): """Solves the maze""" @@ -115,8 +120,9 @@ class MazeSolver: # push onto the queue (which becomes heapq), element containing values # we use heapq so the element with lowest heuristic value will always # be at the top of heap + heuristic = self.heuristic_manhattan(self.start) heapq.heappush( - queue, (self.heuristic_random(self.start), self.start, [self.start]) + queue, (heuristic, self.start, [self.start]) ) # Go through queue until it'maze_data empty @@ -131,19 +137,31 @@ class MazeSolver: # and the maze'maze_data end def heuristic_manhattan(self, position): """Heuristic function that uses Manhattan distance""" - return abs(position[0] - self.end[0]) + abs(position[1] - self.end[1]) + start_time = time.perf_counter() + heuristic = abs(position[0] - self.end[0]) + abs(position[1] - self.end[1]) + end_time = time.perf_counter() + heuristic_time = end_time - start_time + return heuristic, heuristic_time # This heuristic returns the Euclidean distance between the given position # and the maze'maze_data end def heuristic_euclidean(self, position): """Heuristic function that uses Euclidean distance""" - return ( + start_time = time.perf_counter() + heuristic = ( abs(position[0] - self.end[0]) ** 2 + abs(position[1] - self.end[1]) ** 2 ) ** 0.5 + end_time = time.perf_counter() + heuristic_time = end_time - start_time + return heuristic, heuristic_time def heuristic_random(self, position): """Heuristic function that just returns random value between 0 and 1""" - return random() + start_time = time.perf_counter() + heuristic = random() + end_time = time.perf_counter() + heuristic_time = end_time - start_time + return heuristic, heuristic_time # Open and load text file to array @@ -265,6 +283,8 @@ def test_mode(): sum_of_paths = 0 files_amount = 0 sum_of_time = 0 + heuristic_total_total_time = 0 + all_heuristic_called = 0 for filename in os.listdir(FOLDER_NAME): filename_directory = os.path.join(FOLDER_NAME, filename) # Open and load text file to array @@ -273,7 +293,9 @@ def test_mode(): solver_test = MazeSolver(loaded_maze, TEST_MODE) # Find path using MazeSolver solve method start_time = time.perf_counter() - solved_path = solver_test.solve() + solved_path, heuristic_total_time, heuristics_called = solver_test.solve() + heuristic_total_total_time += heuristic_total_time + all_heuristic_called += heuristics_called end_time = time.perf_counter() sum_of_time += end_time - start_time sum_of_paths += len(solved_path) @@ -281,7 +303,7 @@ def test_mode(): files_amount += 1 average_path = sum_of_paths / files_amount average_time = sum_of_time / files_amount - print(f"For: {files_amount} files, sum of path lengths = {sum_of_paths}, average path length = {average_path}, sum_of_time = {sum_of_time}, average time to solve: {average_time}") + print(f"For: {files_amount} files, sum of path lengths = {sum_of_paths}, average path length = {average_path}, sum_of_time = {sum_of_time}, average time to solve: {average_time}, heuristic_total_total_time: {heuristic_total_total_time}, all_heuristic_called: {all_heuristic_called}, average_heuristic_time: {heuristic_total_total_time / all_heuristic_called}") def default(): """ Runs default operation - reads, solves and prints single maze from file """ From 155c718df6b6df2dcaa67856113f0de4fb9a0325 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Wed, 22 Mar 2023 20:08:29 +0100 Subject: [PATCH 8/9] feat: update readme, add important and make pylint compliant --- IMPORTANT.txt | 8 ++++++++ README.txt | 13 +++++++++++++ main.py | 14 ++++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 IMPORTANT.txt diff --git a/IMPORTANT.txt b/IMPORTANT.txt new file mode 100644 index 00000000..b80a31ca --- /dev/null +++ b/IMPORTANT.txt @@ -0,0 +1,8 @@ +Manhattan: +For: 1000 files, sum of path lengths = 122194, average path length = 122.194, sum_of_time = 0.8560767570011194, average time to solve: 0.0008560767570011194, heuristic_total_total_time: 0.10561735888813928, all_heuristic_called: 282721, average_heuristic_time: 3.735745094568118e-07 + +Euclidean: +For: 1000 files, sum of path lengths = 120936, average path length = 120.936, sum_of_time = 1.268625628994414, average time to solve: 0.001268625628994414, heuristic_total_total_time: 0.34228658579650073, all_heuristic_called: 276120, average_heuristic_time: 1.2396298196309602e-06 + +Random: +For: 1000 files, sum of path lengths = 131126, average path length = 131.126, sum_of_time = 1.2832818229990153, average time to solve: 0.0012832818229990153, heuristic_total_total_time: 0.07322999603366043, all_heuristic_called: 417262, average_heuristic_time: 1.7550123431719262e-07 \ No newline at end of file diff --git a/README.txt b/README.txt index aa1e675d..92816df8 100644 --- a/README.txt +++ b/README.txt @@ -4,4 +4,17 @@ python main.py you can specify the name of the file which stores maze by typing: python main.py mazeFile.txt +python main.py -h --help print help prompt + +python main.py -t --test non interactive (does not print steps) for testing +different heuristics, goes through entire generatedMazes folder and +compares heuristic speed and path length, saves solved mazes to solvedMazes folder + +python main.py -t --test [FOLDER] non interactive (does not print steps) for testing +different heuristics, goes through entire [FOLDER] folder and +compares heuristic speed and path length, saves solved mazes to solvedMazes folder + +python main.py -g --generate [NUMBER] - generates as many mazes as entered in +Number parameter and puts it in the generatedMazes folder + diff --git a/main.py b/main.py index 038f426f..95dcacd3 100644 --- a/main.py +++ b/main.py @@ -155,7 +155,7 @@ class MazeSolver: heuristic_time = end_time - start_time return heuristic, heuristic_time - def heuristic_random(self, position): + def heuristic_random(self): """Heuristic function that just returns random value between 0 and 1""" start_time = time.perf_counter() heuristic = random() @@ -301,9 +301,19 @@ def test_mode(): sum_of_paths += len(solved_path) save_maze(loaded_maze, True, solved_path, filename, 0) files_amount += 1 + if files_amount == 0: + print("no mazes found! Generate some using python main.py -g [NUMBER]") + sys.exit() average_path = sum_of_paths / files_amount average_time = sum_of_time / files_amount - print(f"For: {files_amount} files, sum of path lengths = {sum_of_paths}, average path length = {average_path}, sum_of_time = {sum_of_time}, average time to solve: {average_time}, heuristic_total_total_time: {heuristic_total_total_time}, all_heuristic_called: {all_heuristic_called}, average_heuristic_time: {heuristic_total_total_time / all_heuristic_called}") + print(f"""For: {files_amount} files, + sum of path lengths = {sum_of_paths}, + average path length = {average_path}, + sum_of_time = {sum_of_time}, + average time to solve: {average_time}, + heuristic_total_total_time: {heuristic_total_total_time}, + all_heuristic_called: {all_heuristic_called}, + average_heuristic_time: {heuristic_total_total_time / all_heuristic_called}""") def default(): """ Runs default operation - reads, solves and prints single maze from file """ From cf9f4f108f8d19075fb90223131e4b841cd25091 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Tue, 28 Mar 2023 18:42:33 +0200 Subject: [PATCH 9/9] chore: change heuristic to euclidean, make random heuristic work --- main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 95dcacd3..89baafe3 100644 --- a/main.py +++ b/main.py @@ -100,7 +100,7 @@ class MazeSolver: for neighbor in self.get_neighbors(current): if neighbor not in visited: new_path = path + [neighbor] - heuristic, heuristic_time = self.heuristic_manhattan(neighbor) + heuristic, heuristic_time = self.heuristic_euclidean(neighbor) heuristic_total_time += heuristic_time heuristics_called += 1 heapq.heappush( @@ -120,7 +120,7 @@ class MazeSolver: # push onto the queue (which becomes heapq), element containing values # we use heapq so the element with lowest heuristic value will always # be at the top of heap - heuristic = self.heuristic_manhattan(self.start) + heuristic = self.heuristic_euclidean(self.start) heapq.heappush( queue, (heuristic, self.start, [self.start]) ) @@ -155,7 +155,7 @@ class MazeSolver: heuristic_time = end_time - start_time return heuristic, heuristic_time - def heuristic_random(self): + def heuristic_random(self, position): """Heuristic function that just returns random value between 0 and 1""" start_time = time.perf_counter() heuristic = random()