[2] add BFS and DFS
This commit is contained in:
parent
3032b5283b
commit
1dcc56d0bc
|
|
@ -1,8 +1,8 @@
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
class Tile:
|
class Tile:
|
||||||
"""Клетка лабиринта"""
|
|
||||||
def __init__(self, x, y):
|
def __init__(self, x, y):
|
||||||
self._x = x
|
self._x = x
|
||||||
self._y = y
|
self._y = y
|
||||||
|
|
@ -11,43 +11,25 @@ class Tile:
|
||||||
self._goal = False
|
self._goal = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def x(self):
|
def x(self): return self._x
|
||||||
return self._x
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def y(self):
|
def y(self): return self._y
|
||||||
return self._y
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_wall(self):
|
def is_wall(self): return self._wall
|
||||||
return self._wall
|
|
||||||
|
|
||||||
@is_wall.setter
|
@is_wall.setter
|
||||||
def is_wall(self, value):
|
def is_wall(self, value): self._wall = value
|
||||||
self._wall = value
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_entry(self):
|
def is_entry(self): return self._entry
|
||||||
return self._entry
|
|
||||||
|
|
||||||
@is_entry.setter
|
@is_entry.setter
|
||||||
def is_entry(self, value):
|
def is_entry(self, value): self._entry = value
|
||||||
self._entry = value
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_goal(self):
|
def is_goal(self): return self._goal
|
||||||
return self._goal
|
|
||||||
|
|
||||||
@is_goal.setter
|
@is_goal.setter
|
||||||
def is_goal(self, value):
|
def is_goal(self, value): self._goal = value
|
||||||
self._goal = value
|
def can_walk(self): return not self._wall
|
||||||
|
|
||||||
def can_walk(self):
|
|
||||||
return not self._wall
|
|
||||||
|
|
||||||
|
|
||||||
class Labyrinth:
|
class Labyrinth:
|
||||||
"""Лабиринт – двумерная сетка клеток"""
|
|
||||||
def __init__(self, width, height):
|
def __init__(self, width, height):
|
||||||
self._width = width
|
self._width = width
|
||||||
self._height = height
|
self._height = height
|
||||||
|
|
@ -56,20 +38,13 @@ class Labyrinth:
|
||||||
self._exit = None
|
self._exit = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def width(self):
|
def width(self): return self._width
|
||||||
return self._width
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def height(self):
|
def height(self): return self._height
|
||||||
return self._height
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def start(self):
|
def start(self): return self._start
|
||||||
return self._start
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def exit(self):
|
def exit(self): return self._exit
|
||||||
return self._exit
|
|
||||||
|
|
||||||
def tile_at(self, x, y):
|
def tile_at(self, x, y):
|
||||||
if 0 <= x < self._width and 0 <= y < self._height:
|
if 0 <= x < self._width and 0 <= y < self._height:
|
||||||
|
|
@ -78,19 +53,16 @@ class Labyrinth:
|
||||||
|
|
||||||
def configure_tile(self, x, y, kind):
|
def configure_tile(self, x, y, kind):
|
||||||
tile = self.tile_at(x, y)
|
tile = self.tile_at(x, y)
|
||||||
if tile is None:
|
if tile is None: return
|
||||||
return
|
|
||||||
if kind == 'wall':
|
if kind == 'wall':
|
||||||
tile.is_wall = True
|
tile.is_wall = True
|
||||||
elif kind == 'entry':
|
elif kind == 'entry':
|
||||||
if self._start:
|
if self._start: self._start.is_entry = False
|
||||||
self._start.is_entry = False
|
|
||||||
tile.is_entry = True
|
tile.is_entry = True
|
||||||
tile.is_wall = False
|
tile.is_wall = False
|
||||||
self._start = tile
|
self._start = tile
|
||||||
elif kind == 'goal':
|
elif kind == 'goal':
|
||||||
if self._exit:
|
if self._exit: self._exit.is_goal = False
|
||||||
self._exit.is_goal = False
|
|
||||||
tile.is_goal = True
|
tile.is_goal = True
|
||||||
tile.is_wall = False
|
tile.is_wall = False
|
||||||
self._exit = tile
|
self._exit = tile
|
||||||
|
|
@ -98,31 +70,26 @@ class Labyrinth:
|
||||||
tile.is_wall = False
|
tile.is_wall = False
|
||||||
|
|
||||||
def neighbours(self, tile):
|
def neighbours(self, tile):
|
||||||
result = []
|
res = []
|
||||||
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
|
for dx, dy in ((0,-1),(0,1),(-1,0),(1,0)):
|
||||||
nx, ny = tile.x + dx, tile.y + dy
|
nb = self.tile_at(tile.x+dx, tile.y+dy)
|
||||||
nbr = self.tile_at(nx, ny)
|
if nb and nb.can_walk():
|
||||||
if nbr and nbr.can_walk():
|
res.append(nb)
|
||||||
result.append(nbr)
|
return res
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class LabyrinthBuilder:
|
class LabyrinthBuilder:
|
||||||
def build(self, filename):
|
def build(self, filename): raise NotImplementedError
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class TextLabyrinthBuilder(LabyrinthBuilder):
|
class TextLabyrinthBuilder(LabyrinthBuilder):
|
||||||
def build(self, filename):
|
def build(self, filename):
|
||||||
with open(filename, 'r', encoding='utf-8') as f:
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
raw = [line.rstrip('\n') for line in f]
|
lines = [line.rstrip('\n') for line in f]
|
||||||
h = len(raw)
|
h = len(lines)
|
||||||
w = max(len(line) for line in raw) if h > 0 else 0
|
w = max(len(l) for l in lines) if h else 0
|
||||||
entries = 0
|
entries = exits = 0
|
||||||
exits = 0
|
|
||||||
lab = Labyrinth(w, h)
|
lab = Labyrinth(w, h)
|
||||||
|
for y, row in enumerate(lines):
|
||||||
for y, row in enumerate(raw):
|
|
||||||
for x, ch in enumerate(row):
|
for x, ch in enumerate(row):
|
||||||
if ch == '#':
|
if ch == '#':
|
||||||
lab.configure_tile(x, y, 'wall')
|
lab.configure_tile(x, y, 'wall')
|
||||||
|
|
@ -135,13 +102,80 @@ class TextLabyrinthBuilder(LabyrinthBuilder):
|
||||||
else:
|
else:
|
||||||
lab.configure_tile(x, y, 'floor')
|
lab.configure_tile(x, y, 'floor')
|
||||||
if entries != 1 or exits != 1:
|
if entries != 1 or exits != 1:
|
||||||
raise ValueError(
|
raise ValueError(f"Некорректный лабиринт: найдено S={entries}, E={exits}")
|
||||||
f"Лабиринт должен содержать ровно один вход (S) и один выход (E). "
|
|
||||||
f"Найдено: S={entries}, E={exits}"
|
|
||||||
)
|
|
||||||
return lab
|
return lab
|
||||||
|
|
||||||
|
|
||||||
|
class Pathfinder:
|
||||||
|
def find_path(self, lab, start, goal): raise NotImplementedError
|
||||||
|
|
||||||
|
def _build_path(self, predecessors, start, goal):
|
||||||
|
path = []
|
||||||
|
cur = goal
|
||||||
|
while cur is not None:
|
||||||
|
path.append(cur)
|
||||||
|
cur = predecessors.get(cur)
|
||||||
|
path.reverse()
|
||||||
|
return path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visited_count(self):
|
||||||
|
return getattr(self, '_visited', 0)
|
||||||
|
|
||||||
|
|
||||||
|
class BFS_Pathfinder(Pathfinder):
|
||||||
|
def find_path(self, lab, start, goal):
|
||||||
|
q = deque([start])
|
||||||
|
preds = {start: None}
|
||||||
|
seen = {start}
|
||||||
|
while q:
|
||||||
|
cur = q.popleft()
|
||||||
|
if cur == goal:
|
||||||
|
self._visited = len(seen)
|
||||||
|
return self._build_path(preds, start, goal)
|
||||||
|
for nb in lab.neighbours(cur):
|
||||||
|
if nb not in seen:
|
||||||
|
seen.add(nb)
|
||||||
|
preds[nb] = cur
|
||||||
|
q.append(nb)
|
||||||
|
self._visited = len(seen)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class DFS_Pathfinder(Pathfinder):
|
||||||
|
def find_path(self, lab, start, goal):
|
||||||
|
stack = [start]
|
||||||
|
preds = {start: None}
|
||||||
|
seen = {start}
|
||||||
|
while stack:
|
||||||
|
cur = stack.pop()
|
||||||
|
if cur == goal:
|
||||||
|
self._visited = len(seen)
|
||||||
|
return self._build_path(preds, start, goal)
|
||||||
|
for nb in lab.neighbours(cur):
|
||||||
|
if nb not in seen:
|
||||||
|
seen.add(nb)
|
||||||
|
preds[nb] = cur
|
||||||
|
stack.append(nb)
|
||||||
|
self._visited = len(seen)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class LabyrinthSolver:
|
||||||
|
def __init__(self, lab):
|
||||||
|
self._lab = lab
|
||||||
|
self._strategy = None
|
||||||
|
|
||||||
|
def set_strategy(self, strategy):
|
||||||
|
self._strategy = strategy
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
if self._strategy is None:
|
||||||
|
return None
|
||||||
|
path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
def show_labyrinth(lab):
|
def show_labyrinth(lab):
|
||||||
os.system('cls' if os.name == 'nt' else 'clear')
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
print('=' * (lab.width * 2 + 4))
|
print('=' * (lab.width * 2 + 4))
|
||||||
|
|
@ -151,24 +185,37 @@ def show_labyrinth(lab):
|
||||||
print(' ', end='')
|
print(' ', end='')
|
||||||
for x in range(lab.width):
|
for x in range(lab.width):
|
||||||
t = lab.tile_at(x, y)
|
t = lab.tile_at(x, y)
|
||||||
if t == lab.start:
|
if t == lab.start: print('S', end=' ')
|
||||||
print('S', end=' ')
|
elif t == lab.exit: print('E', end=' ')
|
||||||
elif t == lab.exit:
|
elif t.is_wall: print('#', end=' ')
|
||||||
print('E', end=' ')
|
else: print('.', end=' ')
|
||||||
elif t.is_wall:
|
|
||||||
print('#', end=' ')
|
|
||||||
else:
|
|
||||||
print('.', end=' ')
|
|
||||||
print()
|
print()
|
||||||
print('=' * (lab.width * 2 + 4))
|
print('=' * (lab.width * 2 + 4))
|
||||||
print(' S – вход E – выход # – стена . – пол')
|
print(' S – вход E – выход # – стена . – пол')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) < 2:
|
maze_file = sys.argv[1] if len(sys.argv) > 1 else 'maze1.txt'
|
||||||
print("Использование: python main.py <путь к файлу лабиринта>")
|
|
||||||
sys.exit(1)
|
|
||||||
maze_file = sys.argv[1]
|
|
||||||
builder = TextLabyrinthBuilder()
|
builder = TextLabyrinthBuilder()
|
||||||
labyrinth = builder.build(maze_file)
|
labyrinth = builder.build(maze_file)
|
||||||
show_labyrinth(labyrinth)
|
show_labyrinth(labyrinth)
|
||||||
|
|
||||||
|
solver = LabyrinthSolver(labyrinth)
|
||||||
|
print("\nВыберите алгоритм: 1 – BFS, 2 – DFS")
|
||||||
|
choice = input("> ").strip()
|
||||||
|
if choice == '1':
|
||||||
|
solver.set_strategy(BFS_Pathfinder())
|
||||||
|
print("Запущен BFS...")
|
||||||
|
elif choice == '2':
|
||||||
|
solver.set_strategy(DFS_Pathfinder())
|
||||||
|
print("Запущен DFS...")
|
||||||
|
else:
|
||||||
|
print("Неверный выбор, выход.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
path = solver.solve()
|
||||||
|
if path:
|
||||||
|
print(f"Путь найден! Длина: {len(path)} клеток")
|
||||||
|
print(f"Посещено клеток: {solver._strategy.visited_count}")
|
||||||
|
else:
|
||||||
|
print("Путь не найден!")
|
||||||
Loading…
Reference in New Issue
Block a user