5. КОД ПРОГРАММЫ
Далее в таблицах содержится код файлов программы:
• test_maze.py. Главный файл (файл запуска).
Данный файл запускается для тестирования всех алгоритмов.
Тип окна: окно командной строки интерпретатора. Ввод: с клавиатуры.
Вывод: в командную строку интерпретатора.
Как работает: вводится количество тестов, минимальные и максимальные размеры лабиринтов для алгоритмов генерации идеальных и неидеальных лабиринтов, выводятся результаты тестирования алгоритмов генерации и алгоритмов поиска кратчайших путей.
• perfect_maze.py. Содержит алгоритмы генерации идеальных лабиринтов.
Данный файл может запускаться для отображения примеров идеальных лабиринтов и тестирования на скорость алгоритмов генерации идеальных лабиринтов.
Тип окна: окно командной строки интерпретатора. Ввод: с клавиатуры, Вывод: в командную строку интерпретатора.
Как работает: вводятся размеры лабиринта, далее выводятся сгенерированные лабиринты всех алгоритмов файла для этих размеров, потом вводится количество тестов, минимальные и максимальные размеры лабиринтов для тестирования на скорость алгоритмов генерации лабиринтов.
• imperfect_maze.py. Содержит алгоритмы генерации неидеальных
лабиринтов.
Данный файл может запускаться для отображения примеров неидеальных лабиринтов и тестирования на скорость алгоритмов генерации неидеальных лабиринтов.
Тип окна: окно командной строки интерпретатора. Ввод: с клавиатуры, Вывод: в командную строку интерпретатора.
Как работает: вводятся размеры лабиринта, далее выводятся сгенерированные лабиринты всех алгоритмов файла для этих размеров, потом вводится количество тестов, минимальные и максимальные размеры лабиринтов для тестирования на скорость алгоритмов генерации лабиринтов.
• solve_maze.py. Содержит алгоритмы поиска кратчайших путей в
лабиринтах.
Данный файл не следует запускать напрямую (ничего не будет происходить).
Все файлы необходимо располагать в одной папке.
Пример того, что может выводить файл-программа test_maze.py, можно посмотреть в таблице вывода после кода всех четырёх файлов.
46
Таблица 25. Код главного файла
test_maze.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Testing shortest path search algorithms in mazes 2D perfect maze generation algorithms:
-Aldous-Broder algorithm (unbiased maze)
-Wilson's algorithm (unbiased maze)
-Backtracking (depth-first search, iterative version)
-Binary tree algorithm
-Division algorithm
-Eller's algorithm
-Growing tree algorithm
-Kruskal's algorithm
-Prim's algorithm
-Prim's algorithm (modified)
-Sidewinder algorithm
2D imperfect maze generation algorithms:
-Serpentine algorithm
-Small rooms algorithm
-Spiral algorithm
Algorithms for finding the shortest paths in 2D mazes:
-A* algorithm (4 heuristics)
-BFS (breadth-first search) (iterative version)
-Dijkstra algorithm
"""
from imperfect_maze import * from perfect_maze import * from solve_maze import *
import gc as garbage_collector
from random import randint, randrange from time import process_time
from typing import Callable, List
def test_algorithms(n_tests: int, min_length: int, max_length: int, maze_functions: List[Callable], maze_n_functions: List[str], solve_maze_functions: List[Callable], solve_maze_n_functions: List[str], *, is_perfect_mazes: bool):
maze_time_functions = [0 for _ in maze_functions]
solve_maze_time_functions = [[0 for _ in maze_n_functions] for _ in solve_maze_n_functions] for i in range(n_tests):
y_size, x_size = (randint(min_length, max_length) // 2) * 2 + 1, ( randint(min_length, max_length) // 2) * 2 + 1
for j, func in enumerate(maze_functions): garbage_collector.disable() maze_time_functions[j] -= process_time() maze = func(y_size, x_size) maze_time_functions[j] += process_time() garbage_collector.enable()
(yFrom, xFrom, yTo, xTo) = 0, 0, 0, 0
while maze[yFrom][xFrom] or maze[yTo][xTo] or (yFrom, xFrom) == (yTo, xTo): yFrom = randrange(y_size)
xFrom = randrange(x_size) yTo = randrange(y_size) xTo = randrange(x_size)
solve_results = []
for k, solve_func in enumerate(solve_maze_functions): garbage_collector.disable() solve_maze_time_functions[k][j] -= process_time() result = solve_func(maze, (yFrom, xFrom), (yTo, xTo)) solve_maze_time_functions[k][j] += process_time() garbage_collector.enable() solve_results.append(result)
if is_perfect_mazes and not solve_results[k]: print('Invalid answer or imperfect maze')
print(*[''.join('█' if col else ' ' for col in row) for row in maze], sep='\n') print((yFrom, xFrom), (yTo, xTo))
47
test_maze.py
print(solve_maze_n_functions[k], solve_results[k]) raise AttributeError
if is_perfect_mazes and k and solve_results[k - 1] != solve_results[k] \
or not is_perfect_mazes and len(solve_results[k - 1]) != len(solve_results[k]): print('Miscellaneous answers')
print(*[''.join('█' if col else ' ' for col in row) for row in maze], sep='\n') print((yFrom, xFrom), (yTo, xTo))
print(solve_maze_n_functions[k - 1], solve_results[k - 1]) print(solve_maze_n_functions[k], solve_results[k])
raise AttributeError
print('Time of execution of maze generation algorithms')
for time, n in sorted(zip(maze_time_functions, maze_n_functions)): print(n + ': ' + str(time))
print('\n' + 'Time of execution of algorithms to find the shortest paths in the mazes (briefly)') for time, sn in sorted(zip(solve_maze_time_functions, solve_maze_n_functions), key=lambda x:
sum(x[0])):
print(sn + ': ' + str(sum(time)))
print('\n' + 'Time of execution of algorithms to find the shortest paths in the mazes (in detail)') for time, sn in sorted(zip(solve_maze_time_functions, solve_maze_n_functions), key=lambda x:
sum(x[0])):
print('#' * 30 + '\n' + 'Function:', sn)
for n, t in sorted(zip(maze_n_functions, time), key=lambda x: x[1]): print(n + ': ' + str(t))
def main():
def get_input(string, begin, end): string += f" ({begin}-{end}):" input_result = input(string)
while not (input_result.isdecimal() and begin <= int(input_result) <= end): input_result = input(string)
return int(input_result)
print('=== Perfect algorithms ===')
n_tests = get_input('Enter the number of tests', 1, 100000)
min_length = get_input('Enter the minimum length of the maze (odd number)', 5, 100000) max_length = get_input('Enter the maximum length of the maze (odd number)', 5, 100000)
maze_functions = [aldous_broder, wilson, backtracking, binary_tree, division, eller, growing_tree, kruskal,
prim, modified_prim, sidewinder]
maze_n_functions = ['Aldous-Broder algorithm', 'Wilson\'s algorithm', 'Backtracking algorithm', 'Binary tree algorithm', 'Division algorithm', 'Eller\'s algorithm', 'Growing tree
algorithm',
'Kruskal\'s algorithm', 'Prim\'s algorithm', 'Prim\'s algorithm (modified)', 'Sidewinder algorithm']
solve_maze_functions = [bfs, dijkstra,
lambda mz, begin, end: a_star(mz, begin, end, Heuristic.octile), lambda mz, begin, end: a_star(mz, begin, end, Heuristic.manhattan), lambda mz, begin, end: a_star(mz, begin, end, Heuristic.chebyshev), lambda mz, begin, end: a_star(mz, begin, end, Heuristic.euclidean)]
solve_maze_n_functions = ['BFS algorithm', 'Dijkstra algorithm', 'A* algorithm (Octile heuristic)', 'A* algorithm (Manhattan heuristic)', 'A* algorithm (Chebyshev heuristic)', 'A* algorithm (Euclidean heuristic)']
test_algorithms(n_tests, min_length, max_length, maze_functions, maze_n_functions, solve_maze_functions,
solve_maze_n_functions, is_perfect_mazes=True)
print('\n', '=== Imperfect algorithms ===', sep='\n') n_tests = get_input('Enter the number of tests', 1, 100000)
min_length = get_input('Enter the minimum length of the maze (odd number)', 5, 100000) max_length = get_input('Enter the maximum length of the maze (odd number)', 5, 100000) maze_functions = [serpentine, small_rooms, spiral]
maze_n_functions = ['Serpentine algorithm', 'Small rooms algorithm', 'Spiral algorithm'] test_algorithms(n_tests, min_length, max_length, maze_functions, maze_n_functions,
solve_maze_functions,
solve_maze_n_functions, is_perfect_mazes=False)
input()
48
test_maze.py
if __name__ == '__main__': main()
Таблица 26. Файл с алгоритмами генерации идеальных лабиринтов
perfect_maze.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Implementation of 2D perfect maze generation algorithms. Algorithms:
-Aldous-Broder algorithm (unbiased maze)
-Wilson's algorithm (unbiased maze)
-Backtracking (depth-first search, iterative version)
-Binary tree algorithm
-Division algorithm
-Eller's algorithm
-Growing tree algorithm
-Kruskal's algorithm
-Prim's algorithm
-Prim's algorithm (modified)
-Sidewinder algorithm
"""
from random import choice, random, randrange, sample, shuffle from typing import List
def aldous_broder(y_max: int, x_max: int) -> List[List[bool]]:
"""
Aldous-Broder 2D perfect maze generation algorithm (unbiased maze) :param y_max: height
:param x_max: width
:return: 2D perfect maze grid
"""
assert y_max % 2 and x_max % 2 and y_max >= 3 and x_max >= 3 grid = [[True for _ in range(x_max)] for _ in range(y_max)] directions = ((2, 0), (-2, 0), (0, 2), (0, -2))
(current_row, current_col) = (randrange(1, y_max, 2), randrange(1, x_max, 2)) grid[current_row][current_col] = False
num_visited = 1
max_visited = ((y_max - 1) // 2 * (x_max - 1) // 2) while num_visited < max_visited:
neighbors = [(current_row + dy, current_col + dx) for dy, dx in directions]
valid_neighbors = [(y, x) for y, x in neighbors if 0 < y < y_max and 0 < x < x_max and grid[y][x]] if not valid_neighbors:
free_neighbors = [(y, x) for y, x in neighbors if 0 < y < y_max and 0 < x < x_max and not
grid[y][x]]
(current_row, current_col) = choice(free_neighbors) continue
shuffle(valid_neighbors)
for new_row, new_col in valid_neighbors: if grid[new_row][new_col]:
grid[new_row][new_col] = grid[(new_row + current_row) // 2][(new_col + current_col) // 2]
= False
(current_row, current_col) = (new_row, new_col) num_visited += 1
break
return grid
def wilson(y_max: int, x_max: int) -> List[List[bool]]:
"""
Wilson's 2D perfect maze generation algorithm (unbiased maze) :param y_max: height
:param x_max: width
:return: 2D perfect maze grid
"""
assert y_max % 2 and x_max % 2 and y_max >= 3 and x_max >= 3
49
perfect_maze.py
grid = [[True for _ in range(x_max)] for _ in range(y_max)] directions = ((2, 0), (-2, 0), (0, 2), (0, -2))
free = {(y, x) for y in range(1, y_max, 2) for x in range(1, x_max, 2)} (y, x) = (2 * randrange(y_max // 2) + 1, 2 * randrange(x_max // 2) + 1) grid[y][x] = False
free.remove((y, x)) while free:
y, x = key = sample(free, 1)[0] free.remove(key)
path = [key] grid[y][x] = False
neighbors = ((y + dy, x + dx) for dy, dx in directions)
neighbors = [(y, x) for y, x in neighbors if 0 < y < y_max and 0 < x < x_max] y, x = key = choice(neighbors)
while grid[y][x]:
grid[y][x] = grid[(y + path[-1][0]) // 2][(x + path[-1][1]) // 2] = False neighbors = ((y + dy, x + dx) for dy, dx in directions)
neighbors = [(y, x) for y, x in neighbors if 0 < y < y_max and 0 < x < x_max] neighbors.remove(path[-1])
free.remove(key) path.append(key)
y, x = key = choice(neighbors) if key in path:
last_key = path.pop() free.add(last_key) grid[last_key[0]][last_key[1]] = True for key in reversed(path):
free.add(key)
grid[key[0]][key[1]] = grid[(last_key[0] + key[0]) // 2][(last_key[1] + key[1]) // 2] =
True
last_key = key
else:
grid[(y + path[-1][0]) // 2][(x + path[-1][1]) // 2] = False return grid
def backtracking(y_max: int, x_max: int) -> List[List[bool]]:
"""
Iterative version of depth-first search 2D perfect maze generation algorithm :param y_max: height
:param x_max: width
:return: 2D perfect maze grid
"""
grid = [[True for _ in range(x_max)] for _ in range(y_max)] directions = [(0, -2), (0, 2), (-2, 0), (2, 0)]
stack = [(2 * randrange(y_max // 2) + 1, 2 * randrange(x_max // 2) + 1)] while stack:
y, x = stack.pop() grid[y][x] = False
neighbors = ((y + dy, x + dx) for dy, dx in directions)
neighbors = [(y, x) for y, x in neighbors if 0 < y < y_max and 0 < x < x_max and grid[y][x]] if len(neighbors) > 1:
stack.append((y, x)) if neighbors:
ny, nx = choice(neighbors)
grid[(y + ny) // 2][(x + nx) // 2] = False stack.append((ny, nx))
return grid
def binary_tree(y_max: int, x_max: int) -> List[List[bool]]:
"""
Binary tree 2D perfect maze generation algorithm :param y_max: height
:param x_max: width
:return: 2D perfect maze grid
"""
assert y_max % 2 and x_max % 2 and y_max >= 3 and x_max >= 3 grid = [[True for _ in range(x_max)] for _ in range(y_max)] grid[1][1] = False
50