mirror of
https://github.com/kuhyx/WUT_Computer_Science.git
synced 2026-07-04 18:43:15 +02:00
chore: lint my beautiful code
This commit is contained in:
parent
be6d43bc0e
commit
b84ed11ee5
211
main.py
211
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())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user