[2] task2 #311

Merged
VladimirGub merged 1 commits from volkovim/2026-rff_mp:task2 into develop 2026-05-30 12:03:07 +00:00
28 changed files with 1214 additions and 0 deletions
Showing only changes of commit e6c58ac054 - Show all commits

View File

@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
pass

View File

@ -0,0 +1,102 @@
from builders.maze_builder import MazeBuilder
from core.cell import Cell
from core.maze import Maze
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, "r", encoding="utf-8") as source:
raw_lines = [line.rstrip("\n") for line in source]
if not raw_lines:
raise ValueError("Maze file is empty")
expected_width = len(raw_lines[0])
blueprint = []
start_cell = None
exit_cell = None
for y_index, raw in enumerate(raw_lines):
if len(raw) != expected_width:
raise ValueError(
f"Broken maze shape at line {y_index + 1}"
)
row_pack = []
for x_index, symbol in enumerate(raw):
current = None
if symbol == "#":
current = Cell(
x_index,
y_index,
isWall=True
)
elif symbol == " ":
current = Cell(
x_index,
y_index
)
elif symbol == "S":
if start_cell:
raise ValueError(
"Multiple start cells detected"
)
current = Cell(
x_index,
y_index,
isStart=True
)
start_cell = current
elif symbol == "E":
if exit_cell:
raise ValueError(
"Multiple exit cells detected"
)
current = Cell(
x_index,
y_index,
isExit=True
)
exit_cell = current
else:
raise ValueError(
f"Unsupported symbol '{symbol}' "
f"at ({x_index}, {y_index})"
)
row_pack.append(current)
blueprint.append(row_pack)
if start_cell is None:
raise ValueError(
"Start cell S not found"
)
if exit_cell is None:
raise ValueError(
"Exit cell E not found"
)
return Maze(
blueprint,
start_cell=start_cell,
exit_cell=exit_cell
)

View File

@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass

View File

@ -0,0 +1,65 @@
from command.command import Command
class MoveCommand(Command):
def __init__(
self,
player,
maze,
direction
):
self.player = player
self.maze = maze
self.direction = direction
self.previous = None
def _targetCell(self):
offsets = {
"W": (0, -1),
"S": (0, 1),
"A": (-1, 0),
"D": (1, 0)
}
dx, dy = offsets.get(
self.direction.upper(),
(0, 0)
)
x, y = self.player.getPosition()
return self.maze.getCell(
x + dx,
y + dy
)
def execute(self):
destination = self._targetCell()
if destination is None:
return False
if not destination.isPassable():
return False
self.previous = self.player.current
self.player.place(
destination
)
return True
def undo(self):
if self.previous is None:
return False
self.player.place(
self.previous
)
return True

View File

@ -0,0 +1,13 @@
class Player:
def __init__(self, start_cell):
self.current = start_cell
def place(self, cell):
self.current = cell
def getPosition(self):
return self.current.getPosition()
def __str__(self):
x, y = self.getPosition()
return f"Player({x}, {y})"

View File

@ -0,0 +1,40 @@
class Cell:
def __init__(
self,
x: int,
y: int,
isWall: bool = False,
isStart: bool = False,
isExit: bool = False
):
self.x = x
self.y = y
self.isWall = isWall
self.isStart = isStart
self.isExit = isExit
def isPassable(self) -> bool:
return self.isWall is False
def getPosition(self):
return self.x, self.y
def __str__(self):
if self.isStart:
return "S"
if self.isExit:
return "E"
if self.isWall:
return "#"
return " "
def __repr__(self):
return (
f"Cell(x={self.x}, y={self.y}, "
f"wall={self.isWall}, "
f"start={self.isStart}, "
f"exit={self.isExit})"
)

View File

@ -0,0 +1,59 @@
from core.cell import Cell
class Maze:
def __init__(self, cells_map, start_cell=None, exit_cell=None):
self.cells = cells_map
self.start = start_cell
self.exit = exit_cell
self.height = len(cells_map)
self.width = len(cells_map[0]) if self.height else 0
def getCell(self, x: int, y: int):
if y < 0 or y >= self.height:
return None
if x < 0 or x >= self.width:
return None
return self.cells[y][x]
def getNeighbors(self, current: Cell):
reachable = []
top = self.getCell(current.x, current.y - 1)
right = self.getCell(current.x + 1, current.y)
bottom = self.getCell(current.x, current.y + 1)
left = self.getCell(current.x - 1, current.y)
for candidate in (top, right, bottom, left):
if candidate is None:
continue
if candidate.isPassable():
reachable.append(candidate)
return reachable
def hasStart(self):
return self.start is not None
def hasExit(self):
return self.exit is not None
def size(self):
return self.width, self.height
def __str__(self):
rows = []
for line in self.cells:
visual = ""
for cell in line:
visual += str(cell)
rows.append(visual)
return "\n".join(rows)

View File

@ -0,0 +1,22 @@
class SearchStats:
def __init__(
self,
strategy_name,
elapsed_ms,
visited_cells,
path_length
):
self.strategy_name = strategy_name
self.elapsed_ms = elapsed_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
lines = [
f"Strategy: {self.strategy_name}",
f"Time: {self.elapsed_ms:.3f} ms",
f"Visited: {self.visited_cells}",
f"Path length: {self.path_length}"
]
return "\n".join(lines)

View File

@ -0,0 +1,93 @@
import csv
from solver.maze_solver import MazeSolver
class BenchmarkRunner:
def __init__(
self,
maze,
strategies,
cycles=5
):
self.maze = maze
self.strategies = strategies
self.cycles = cycles
def launch(self):
report = []
for strategy in self.strategies:
solver = MazeSolver(
self.maze,
strategy
)
total_time = 0
total_visited = 0
total_path = 0
for _ in range(self.cycles):
_, stats = solver.solve()
total_time += stats.time_ms
total_visited += stats.visited_cells
total_path += stats.path_length
report.append(
{
"maze": "",
"strategy":
strategy.__class__.__name__,
"time_ms":
round(
total_time / self.cycles,
4
),
"visited_cells":
round(
total_visited / self.cycles,
2
),
"path_length":
round(
total_path / self.cycles,
2
)
}
)
return report
def exportCSV(
self,
filename,
results
):
with open(
filename,
"w",
newline="",
encoding="utf-8"
) as file:
writer = csv.DictWriter(
file,
fieldnames=[
"maze",
"strategy",
"time_ms",
"visited_cells",
"path_length"
]
)
writer.writeheader()
for row in results:
writer.writerow(row)

View File

@ -0,0 +1,161 @@
import csv
import matplotlib.pyplot as plt
class ChartBuilder:
def __init__(
self,
csv_file
):
self.csv_file = csv_file
def _read(self):
rows = []
with open(
self.csv_file,
"r",
encoding="utf-8"
) as file:
reader = csv.DictReader(file)
for row in reader:
rows.append(row)
return rows
def buildTimeChart(self):
rows = self._read()
labels = []
values = []
for row in rows:
labels.append(
f"{row['maze']}\n"
f"{row['strategy']}"
)
values.append(
float(
row["time_ms"]
)
)
plt.figure()
plt.bar(
labels,
values
)
plt.title(
"Search Time"
)
plt.ylabel(
"Milliseconds"
)
plt.xticks(
rotation=45
)
plt.tight_layout()
plt.show()
def buildVisitedChart(self):
rows = self._read()
labels = []
values = []
for row in rows:
labels.append(
f"{row['maze']}\n"
f"{row['strategy']}"
)
values.append(
float(
row[
"visited_cells"
]
)
)
plt.figure()
plt.bar(
labels,
values
)
plt.title(
"Visited Cells"
)
plt.ylabel(
"Cells"
)
plt.xticks(
rotation=45
)
plt.tight_layout()
plt.show()
def buildPathChart(self):
rows = self._read()
labels = []
values = []
for row in rows:
labels.append(
f"{row['maze']}\n"
f"{row['strategy']}"
)
values.append(
float(
row[
"path_length"
]
)
)
plt.figure()
plt.bar(
labels,
values
)
plt.title(
"Path Length"
)
plt.ylabel(
"Cells"
)
plt.xticks(
rotation=45
)
plt.tight_layout()
plt.show()

View File

@ -0,0 +1,16 @@
maze,strategy,time_ms,visited_cells,path_length
small.txt,BFSStrategy,0.0676,53.0,23.0
small.txt,DFSStrategy,0.0527,31.0,31.0
small.txt,AStarStrategy,0.0682,46.0,23.0
medium.txt,BFSStrategy,0.7144,717.0,431.0
medium.txt,DFSStrategy,0.6878,737.0,431.0
medium.txt,AStarStrategy,0.6968,591.0,431.0
large.txt,BFSStrategy,2.103,2491.0,1171.0
large.txt,DFSStrategy,2.7719,3019.0,1243.0
large.txt,AStarStrategy,2.5953,1995.0,1171.0
empty.txt,BFSStrategy,0.027,19.0,8.0
empty.txt,DFSStrategy,0.0115,8.0,8.0
empty.txt,AStarStrategy,0.0151,8.0,8.0
blocked.txt,BFSStrategy,0.002,1.0,0.0
blocked.txt,DFSStrategy,0.0013,1.0,0.0
blocked.txt,AStarStrategy,0.0019,1.0,0.0
1 maze strategy time_ms visited_cells path_length
2 small.txt BFSStrategy 0.0676 53.0 23.0
3 small.txt DFSStrategy 0.0527 31.0 31.0
4 small.txt AStarStrategy 0.0682 46.0 23.0
5 medium.txt BFSStrategy 0.7144 717.0 431.0
6 medium.txt DFSStrategy 0.6878 737.0 431.0
7 medium.txt AStarStrategy 0.6968 591.0 431.0
8 large.txt BFSStrategy 2.103 2491.0 1171.0
9 large.txt DFSStrategy 2.7719 3019.0 1243.0
10 large.txt AStarStrategy 2.5953 1995.0 1171.0
11 empty.txt BFSStrategy 0.027 19.0 8.0
12 empty.txt DFSStrategy 0.0115 8.0 8.0
13 empty.txt AStarStrategy 0.0151 8.0 8.0
14 blocked.txt BFSStrategy 0.002 1.0 0.0
15 blocked.txt DFSStrategy 0.0013 1.0 0.0
16 blocked.txt AStarStrategy 0.0019 1.0 0.0

70
volkovim/task2/main.py Normal file
View File

@ -0,0 +1,70 @@
from builders.text_file_builder import TextFileMazeBuilder
from strategies.bfs import BFSStrategy
from strategies.dfs import DFSStrategy
from strategies.astar import AStarStrategy
from experiments.benchmark import BenchmarkRunner
from experiments.plots import ChartBuilder
builder = TextFileMazeBuilder()
maze_files = [
"small.txt",
"medium.txt",
"large.txt",
"empty.txt",
"blocked.txt"
]
all_results = []
for maze_file in maze_files:
print()
print("Loading:", maze_file)
maze = builder.buildFromFile(
f"mazes/{maze_file}"
)
runner = BenchmarkRunner(
maze,
[
BFSStrategy(),
DFSStrategy(),
AStarStrategy()
],
cycles=10
)
results = runner.launch()
for row in results:
row["maze"] = maze_file
all_results.extend(results)
runner.exportCSV(
"experiments/results.csv",
all_results
)
print()
print("CSV created")
charts = ChartBuilder(
"experiments/results.csv"
)
print("Time chart...")
charts.buildTimeChart()
print("Visited chart...")
charts.buildVisitedChart()
print("Path chart...")
charts.buildPathChart()
print("Done")

View File

@ -0,0 +1,3 @@
##########
#S#####E##
##########

View File

@ -0,0 +1,5 @@
##########
#S E#
# #
# #
##########

View File

@ -0,0 +1,100 @@
S # # # # # # # # # # # # # #
# # ####### ### # ##### # # # # # # # # ### # # ### # # ### # ##### # ##### ### ### ### ######### #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### ##### ### ### ##### ##### ####### # ##### ####### # # # ####### ##### # ######### ### ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ##### # # ##### # # ####### # ### ##### # # # # ### ### ######### ##### # ##### # # ### ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
##### # # # ### ### ####### # # ### ######### ### ### ### ### # # # # # ### ##### # # ### # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
### # ### # ##### ##### ######### ######### # ##### ### # ####### # ##### ### ####### # ### # ### #
# # # # # # # # # # # # # # # # # # # # #
# ################# # ### # # # # # ########### ############# # ##### ##### ##### # ##### ### # ###
# # # # # # # # # # # # # # # # # # # # # #
### # ### # ### ####### # ##### ######### ### # ### ### # ####### # ### ####### ##### # ### # # # #
# # # # # # # # # # # # # # # # # # # # # # #
######### ### ### # ### # ####### ##### # # # ####### ##### ####### # ########### ####### #########
# # # # # # # # # # # # # # # # # # # # #
## # ####### ### ##### # ####### ### # # # # ##### ### ########### # # # ### # ##### # # ### ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
### # # ########### ######### # ### # # ### ### ####### ### # # # # ##### # ### ##### # # ### ### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
## ##### # # # # # ### # ### ##### # # # ##### # ### # ### # # # # # # # ##### ### ####### # ### ###
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
##### ### # # # # # ######### ### ### ### # ##### ##### ### # ##### # ######### ############# ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ##### # # ### ### ##### # ### ### ##### # # ### # ##### ##### ### ### # ##### ### # # ### # # ###
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ####### ####### ### ### ######### ### # # ##### # ### ########### ### # ### ####### ### # # #
# # # # # # # # # # # # # # # # # # # # # #
################### # # # # ### # # # ######### # # ### ############### # ### ##### ### ### ##### #
# # # # # # # # # # # # # # # # # # # # #
##### # # ### # # ### # ####### # # ##### # # ### ##### # ####### # # # ######### # ######### #####
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
#### # # ### # # ### # ### ####### ##### # # ### # # ##### # # ####### # # # ##### ### # # ####### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
## # # # # ### ### # ### ### # # # ### # ####### ### # ########### # ##### ##### # # ### ### ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # #
########### # # ########### ### # # # # # ### # # ### # ####### # ######### # # ### # ######### ###
# # # # # # # # # # # # # # # # # # # # # # # #
######## # # # ### # ########### # # ####### ### # # # ####### ### # # # ### # ### ######### # # # #
# # # # # # # # # # # # # # # # # # # # # # #
##### ##### ### # # # ####### ### ### # # # # ############# # ##### # ### ##### ######### ### ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
### ### # ### # ##### # # # ### # # # ### # ### ### # # # # ########### ####### # ##### ####### ###
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
## ####### # ##### # ### ### # # # # # # # ####### ### # # # # # # # # ### # # ##### # # # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
### # # # ##### ####### # ### # ### ### ####### # # ### ####### ### ####### # ### ####### # # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ##### ##### # # ### ### # ######### ### ### ### ### ####### ##### # ######### ##### # ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ####### # ##### ### # # # # # ### ### # ### ### ######### # ####### # ####### ### ######### ###
# # # # # # # # # # # # # # # # # # # # # # # # # # #
######### # ### ### # # # # # # ####### ##### ### # # ##### # ### # # ####### # # # ### # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
## # # # ### ##### # ######### # # ####### ### ### # ##### ######### # # # ####### ### # # # # ### #
# # # # # # # # # # # # # # # # # # # # # # #
### ######### ####### # # ######### # # ### ### ### ##### ### # ########### ######### # # # ### ###
# # # # # # # # # # # # # # # # # # # # # # # #
## ######### # ### # ####### ### # ### ### # # ####### # ### ##### ### # # ### # ### ##### # # ### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ### ### # # # ##### # ### # ### ### # # # ####### ##### ### ### # ##### # ##### # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ### ### # ### # ######### # ######### # ### # # # ### ##### ### # ### # # ### # ##### ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # #
##### # ### ##### ####### ### # ##### # # # ### ### # ##### # # ### # ####### ##### # # # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
### # ### ######### # ##### ### # ### # # # ############# ### ### # ##### # ### ######### # ### # #
# # # # # # # # # # # # # # # # # # # # # # # #
## # # # ### ##### # ##### # ### ### # # # ### # # ######### # # ##### ### ####### ### # ######### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ############### ### ####### ### # ### ####### # # # # ######### # ### # # # ##### ##### # # #####
# # # # # # # # # # # # # # # # # # # # # #
##### # # ### # ### ######### # ######### # # ### # # ####### # ########### # # # ##### ####### # #
# # # # # # # # # # # # # # # # # # # # # # #
## # ####### ### # # # ############# ### ### ### ### ##### # # ### ########### ##### # ### ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
### ### # # # ##### ### # # ##### ####### # # ### ### # # ##### # # ####### ##### # # # # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
###### ##### ### # ### ### # # ########### # # # ##### # ### ##### # # ### ######### ### ##### # ###
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # ### ####### ######### # # ######### ### # ##### ##### ##### ### # # # # # # ### ####### #
# # # # # # # # # # # # # # # # # # # # # # # #
#### # ##### # # # ##### ### # ### # ##### ### # ##### ### ######### ### ### ########### # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
######### ##### # ####### # # # ##### # ### # # # # ####### ##### # # ### ### # # # # ### # # ### #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ### # ##### # # # ############### ### ######### ### # ##### # # ######### # ### # ### # # # # ###
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ####### # ### # ##### ##### # # ### # ### # # ##### # ### # # # ####### # ##### # # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
##### # ### # # # ##### ##### # # # ### ### # # # # ######### # ########### # # ##### ####### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
## # # # # ##### # ### ####### ########### ### # # ####### # # ##### # # # ### ##### ##### # ##### #
# # # # # # # # # # # # # # # # # # # # # #
### ##### ########### # ### # # ####### # # # ############# # ### ### # ### ######### # ### ### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
##### # # # ##### ####### # ### # # ##### # ### ####### ######### # ########### ### # ######### # #
# # # # # # # # #
################################################################################################## E

View File

@ -0,0 +1,50 @@
S # # # #
####### ### # # ########### ##### # ##### ##### #
# # # # # # # # # #
## # # ### ####### ##### ####### # ### ######### #
# # # # # # # # # # # # #
####### # # # # ### ##### # ### ### # # # # ### #
# # # # # # # # # # # # # # # # #
# ### # ### # ### ### # # ### ### ####### # # ###
# # # # # # # # # # # # # #
# # ############# # ### ### # ######### # # ### #
# # # # # # # # #
########### ########### # ##### ### ### # # # ###
# # # # # # # # # # # # # #
# # ####### # ### # ##### ### ### ### ### # # # #
# # # # # # # # # # # # # # # #
###### ### ### # # # # # # # ### ### ##### # # # #
# # # # # # # # # # # # # # #
### # # ######### ### # # # # ####### ##### # # #
# # # # # # # # # # # # # #
### ### # ##### # # ######### # # # # ##### # # #
# # # # # # # # # # # #
## # ######### # # ### ### # ### ######### ##### #
# # # # # # # # # # # #
##### # ### # ### ##### # # # ####### ##### # # #
# # # # # # # # # # # # # # #
## # ##### # # ##### ##### ### ### # ### # # # ###
# # # # # # # # # # # # #
##### # ### # # ##### ### # ### ######### # #####
# # # # # # # # # # #
# ####### ######### ### ####### # # ####### ### #
# # # # # # # # # # # # # #
# # ####### # # ##### # # ### ### # # # # ##### #
# # # # # # # # # # # # # # # #
# ##### # ####### # # # # # ### # ### # # # ### #
# # # # # # # # # # # # # # #
# ########### # ### ####### ### # ### # # # # # #
# # # # # # # # # # # # #
# # ####### ##### ########### ##### # # ##### # #
# # # # # # # # # # #
### ### ### # ############### # # # ##### ### ###
# # # # # # # # # # # # # #
# ### ### # ### ##### # # # # # ##### # ### # # #
# # # # # # # # # # # # # #
# # ####### # ### ######### ######### ### # # # #
# # # # # # # # # # # # # # #
##### # ####### # # # ### # # # # # ### ### # # #
# # # # # # # # # # # # # # #
## ### ##### ####### ### # # ### ##### # ### ### #
# # # #
################################################ E

View File

@ -0,0 +1,10 @@
S #
### ### #
# # #
## # # ###
# # #
####### #
# #
## # #####
######## E

View File

@ -0,0 +1,64 @@
from observer.observer import Observer
class ConsoleView(Observer):
def update(self, event):
event_type = event.get("type")
if event_type == "maze_loaded":
print("[VIEW] Maze loaded")
elif event_type == "search_started":
print("[VIEW] Search started")
elif event_type == "search_finished":
print("[VIEW] Search completed")
elif event_type == "path_found":
print(
f"[VIEW] Path length: "
f"{event.get('length')}"
)
def render(
self,
maze,
path=None
):
route_marks = set()
if path:
for cell in path:
route_marks.add(
cell.getPosition()
)
screen = []
for row in maze.cells:
visual_row = ""
for cell in row:
position = cell.getPosition()
if (
position in route_marks
and not cell.isStart
and not cell.isExit
):
visual_row += "*"
else:
visual_row += str(cell)
screen.append(
visual_row
)
print(
"\n".join(screen)
)

View File

@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, event):
pass

Binary file not shown.

View File

View File

@ -0,0 +1,73 @@
import time
from solver.search_stats import SearchStats
class MazeSolver:
def __init__(
self,
maze,
strategy
):
self.maze = maze
self.strategy = strategy
self.observers = []
def addObserver(
self,
observer
):
self.observers.append(
observer
)
def notify(
self,
event
):
for observer in self.observers:
observer.update(event)
def setStrategy(
self,
strategy
):
self.strategy = strategy
def solve(self):
self.notify(
"search_started"
)
start_time = time.perf_counter()
path, visited_cells = (
self.strategy.findPath(
self.maze,
self.maze.start,
self.maze.exit
)
)
finish_time = (
time.perf_counter()
)
elapsed_ms = (
finish_time
- start_time
) * 1000
stats = SearchStats(
elapsed_ms,
visited_cells,
len(path)
)
self.notify(
"search_finished"
)
return path, stats

View File

@ -0,0 +1,22 @@
class SearchStats:
def __init__(
self,
time_ms,
visited_cells,
path_length
):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return (
f"Time: "
f"{self.time_ms:.4f} ms | "
f"Visited: "
f"{self.visited_cells} | "
f"Path length: "
f"{self.path_length}"
)

View File

@ -0,0 +1,107 @@
import heapq
from itertools import count
from strategies.strategy import PathFindingStrategy
class AStarStrategy(PathFindingStrategy):
def _estimate(self, current, target):
return (
abs(current.x - target.x)
+ abs(current.y - target.y)
)
def findPath(self, maze, start, exit):
frontier = []
sequence = count()
heapq.heappush(
frontier,
(
self._estimate(start, exit),
next(sequence),
0,
start
)
)
ancestry = {}
travel_cost = {
start.getPosition(): 0
}
explored = set()
explored_count = 0
while frontier:
_, _, spent, current = heapq.heappop(
frontier
)
current_mark = current.getPosition()
if current_mark in explored:
continue
explored.add(current_mark)
explored_count += 1
if current == exit:
break
for neighbor in maze.getNeighbors(current):
mark = neighbor.getPosition()
new_cost = spent + 1
if (
mark not in travel_cost
or new_cost < travel_cost[mark]
):
travel_cost[mark] = new_cost
ancestry[mark] = current
priority = (
new_cost
+ self._estimate(
neighbor,
exit
)
)
heapq.heappush(
frontier,
(
priority,
next(sequence),
new_cost,
neighbor
)
)
if (
exit.getPosition() not in ancestry
and exit != start
):
return [], explored_count
route = []
cursor = exit
while cursor != start:
route.append(cursor)
cursor = ancestry[
cursor.getPosition()
]
route.append(start)
route.reverse()
return route, explored_count

View File

@ -0,0 +1,51 @@
from collections import deque
from strategies.strategy import PathFindingStrategy
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
frontier = deque([start])
visited = {
start.getPosition()
}
ancestry = {}
explored_count = 0
while frontier:
current = frontier.popleft()
explored_count += 1
if current == exit:
break
for neighbor in maze.getNeighbors(current):
mark = neighbor.getPosition()
if mark in visited:
continue
visited.add(mark)
ancestry[mark] = current
frontier.append(neighbor)
if exit.getPosition() not in visited:
return [], explored_count
route = []
cursor = exit
while cursor != start:
route.append(cursor)
cursor = ancestry[cursor.getPosition()]
route.append(start)
route.reverse()
return route, explored_count

View File

@ -0,0 +1,52 @@
from strategies.strategy import PathFindingStrategy
class DFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
frontier = [start]
visited = {
start.getPosition()
}
ancestry = {}
explored_count = 0
while frontier:
current = frontier.pop()
explored_count += 1
if current == exit:
break
neighbors = maze.getNeighbors(current)
for neighbor in reversed(neighbors):
point = neighbor.getPosition()
if point in visited:
continue
visited.add(point)
ancestry[point] = current
frontier.append(neighbor)
if exit.getPosition() not in visited:
return [], explored_count
route = []
cursor = exit
while cursor != start:
route.append(cursor)
cursor = ancestry[cursor.getPosition()]
route.append(start)
route.reverse()
return route, explored_count

View File

View File

@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
class PathFindingStrategy(ABC):
@abstractmethod
def findPath(self, maze, start, exit):
pass