[2] add A*
This commit is contained in:
parent
1dcc56d0bc
commit
fc6b64b7cd
|
|
@ -1,6 +1,8 @@
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
import heapq
|
||||||
|
|
||||||
class Tile:
|
class Tile:
|
||||||
def __init__(self, x, y):
|
def __init__(self, x, y):
|
||||||
|
|
@ -109,12 +111,12 @@ class TextLabyrinthBuilder(LabyrinthBuilder):
|
||||||
class Pathfinder:
|
class Pathfinder:
|
||||||
def find_path(self, lab, start, goal): raise NotImplementedError
|
def find_path(self, lab, start, goal): raise NotImplementedError
|
||||||
|
|
||||||
def _build_path(self, predecessors, start, goal):
|
def _build_path(self, preds, start, goal):
|
||||||
path = []
|
path = []
|
||||||
cur = goal
|
cur = goal
|
||||||
while cur is not None:
|
while cur is not None:
|
||||||
path.append(cur)
|
path.append(cur)
|
||||||
cur = predecessors.get(cur)
|
cur = preds.get(cur)
|
||||||
path.reverse()
|
path.reverse()
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
@ -161,10 +163,170 @@ class DFS_Pathfinder(Pathfinder):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class AStar_Pathfinder(Pathfinder):
|
||||||
|
def _heuristic(self, a, b):
|
||||||
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||||
|
|
||||||
|
def find_path(self, lab, start, goal):
|
||||||
|
heap = []
|
||||||
|
cnt = 0
|
||||||
|
f_start = self._heuristic(start, goal)
|
||||||
|
heapq.heappush(heap, (f_start, cnt, start))
|
||||||
|
cnt += 1
|
||||||
|
preds = {}
|
||||||
|
g = {start: 0}
|
||||||
|
f = {start: f_start}
|
||||||
|
seen = set()
|
||||||
|
while heap:
|
||||||
|
cur_f, _, cur = heapq.heappop(heap)
|
||||||
|
seen.add(cur)
|
||||||
|
if cur == goal:
|
||||||
|
self._visited = len(seen)
|
||||||
|
return self._build_path(preds, start, goal)
|
||||||
|
if cur_f > f.get(cur, float('inf')):
|
||||||
|
continue
|
||||||
|
for nb in lab.neighbours(cur):
|
||||||
|
tent_g = g[cur] + 1
|
||||||
|
if tent_g < g.get(nb, float('inf')):
|
||||||
|
preds[nb] = cur
|
||||||
|
g[nb] = tent_g
|
||||||
|
new_f = tent_g + self._heuristic(nb, goal)
|
||||||
|
f[nb] = new_f
|
||||||
|
heapq.heappush(heap, (new_f, cnt, nb))
|
||||||
|
cnt += 1
|
||||||
|
self._visited = len(seen)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class Explorer:
|
||||||
|
def __init__(self, start_tile, labyrinth):
|
||||||
|
self._current = start_tile
|
||||||
|
self._previous = None
|
||||||
|
self._lab = labyrinth
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current(self):
|
||||||
|
return self._current
|
||||||
|
|
||||||
|
def move(self, tile):
|
||||||
|
if tile and tile.can_walk():
|
||||||
|
self._previous = self._current
|
||||||
|
self._current = tile
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self._previous:
|
||||||
|
self._current, self._previous = self._previous, None
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Action:
|
||||||
|
def execute(self): raise NotImplementedError
|
||||||
|
def undo(self): raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class MoveAction(Action):
|
||||||
|
def __init__(self, explorer, direction, lab):
|
||||||
|
self._explorer = explorer
|
||||||
|
self._dx, self._dy = direction
|
||||||
|
self._lab = lab
|
||||||
|
self._done = False
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
nx = self._explorer.current.x + self._dx
|
||||||
|
ny = self._explorer.current.y + self._dy
|
||||||
|
target = self._lab.tile_at(nx, ny)
|
||||||
|
if target and target.can_walk():
|
||||||
|
self._explorer.move(target)
|
||||||
|
self._done = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self._done:
|
||||||
|
self._explorer.undo()
|
||||||
|
self._done = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class GameObserver:
|
||||||
|
def update(self, event, data): raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalDisplay(GameObserver):
|
||||||
|
def __init__(self, explorer=None):
|
||||||
|
self._explorer = explorer
|
||||||
|
self._last_path = None
|
||||||
|
|
||||||
|
def update(self, event, data):
|
||||||
|
if event == 'labyrinth_loaded':
|
||||||
|
self._draw_lab(data)
|
||||||
|
elif event == 'path_found':
|
||||||
|
self._last_path = data
|
||||||
|
self._show_path_info(data)
|
||||||
|
elif event == 'player_moved':
|
||||||
|
self._draw_with_player(data)
|
||||||
|
|
||||||
|
def _draw_lab(self, lab):
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
print('=' * (lab.width * 2 + 4))
|
||||||
|
print(' ЛАБИРИНТ')
|
||||||
|
print('=' * (lab.width * 2 + 4))
|
||||||
|
for y in range(lab.height):
|
||||||
|
print(' ', end='')
|
||||||
|
for x in range(lab.width):
|
||||||
|
t = lab.tile_at(x, y)
|
||||||
|
if t == lab.start: print('S', end=' ')
|
||||||
|
elif t == lab.exit: print('E', end=' ')
|
||||||
|
elif t.is_wall: print('#', end=' ')
|
||||||
|
else: print('.', end=' ')
|
||||||
|
print()
|
||||||
|
print('=' * (lab.width * 2 + 4))
|
||||||
|
print(' S – вход E – выход # – стена . – пол')
|
||||||
|
|
||||||
|
def _draw_with_player(self, lab):
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
|
print('=' * (lab.width * 2 + 4))
|
||||||
|
print(' ЛАБИРИНТ (P – игрок)')
|
||||||
|
print('=' * (lab.width * 2 + 4))
|
||||||
|
for y in range(lab.height):
|
||||||
|
print(' ', end='')
|
||||||
|
for x in range(lab.width):
|
||||||
|
t = lab.tile_at(x, y)
|
||||||
|
if self._explorer and t == self._explorer.current:
|
||||||
|
print('P', end=' ')
|
||||||
|
elif t == lab.start: print('S', end=' ')
|
||||||
|
elif t == lab.exit: print('E', end=' ')
|
||||||
|
elif t.is_wall: print('#', end=' ')
|
||||||
|
else: print('.', end=' ')
|
||||||
|
print()
|
||||||
|
print('=' * (lab.width * 2 + 4))
|
||||||
|
if self._explorer:
|
||||||
|
print(f' Позиция игрока: ({self._explorer.current.x}, {self._explorer.current.y})')
|
||||||
|
print(' S – вход E – выход # – стена . – пол P – игрок')
|
||||||
|
|
||||||
|
def _show_path_info(self, path):
|
||||||
|
if not path:
|
||||||
|
print('\n Путь не найден!')
|
||||||
|
else:
|
||||||
|
print(f'\n Найден путь длиной {len(path)} клеток.')
|
||||||
|
|
||||||
|
|
||||||
class LabyrinthSolver:
|
class LabyrinthSolver:
|
||||||
def __init__(self, lab):
|
def __init__(self, lab):
|
||||||
self._lab = lab
|
self._lab = lab
|
||||||
self._strategy = None
|
self._strategy = None
|
||||||
|
self._observers = []
|
||||||
|
|
||||||
|
def attach(self, obs):
|
||||||
|
self._observers.append(obs)
|
||||||
|
|
||||||
|
def _notify(self, event, data):
|
||||||
|
for obs in self._observers:
|
||||||
|
obs.update(event, data)
|
||||||
|
|
||||||
def set_strategy(self, strategy):
|
def set_strategy(self, strategy):
|
||||||
self._strategy = strategy
|
self._strategy = strategy
|
||||||
|
|
@ -172,50 +334,74 @@ class LabyrinthSolver:
|
||||||
def solve(self):
|
def solve(self):
|
||||||
if self._strategy is None:
|
if self._strategy is None:
|
||||||
return None
|
return None
|
||||||
|
start_t = time.perf_counter()
|
||||||
path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit)
|
path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit)
|
||||||
return path
|
elapsed = (time.perf_counter() - start_t) * 1000
|
||||||
|
self._notify('path_found', path)
|
||||||
|
return {'time_ms': elapsed, 'visited': self._strategy.visited_count, 'length': len(path)}
|
||||||
|
|
||||||
|
|
||||||
def show_labyrinth(lab):
|
def run_interactive(lab):
|
||||||
os.system('cls' if os.name == 'nt' else 'clear')
|
player = Explorer(lab.start, lab)
|
||||||
print('=' * (lab.width * 2 + 4))
|
display = TerminalDisplay(player)
|
||||||
print(' ЛАБИРИНТ')
|
display.update('labyrinth_loaded', lab)
|
||||||
print('=' * (lab.width * 2 + 4))
|
|
||||||
for y in range(lab.height):
|
solver = LabyrinthSolver(lab)
|
||||||
print(' ', end='')
|
solver.attach(display)
|
||||||
for x in range(lab.width):
|
|
||||||
t = lab.tile_at(x, y)
|
print('\n УПРАВЛЕНИЕ:')
|
||||||
if t == lab.start: print('S', end=' ')
|
print(' H – влево J – вниз K – вверх L – вправо')
|
||||||
elif t == lab.exit: print('E', end=' ')
|
print(' U – отменить ход Q – выход')
|
||||||
elif t.is_wall: print('#', end=' ')
|
print(' Автопоиск: B – BFS D – DFS A – A*')
|
||||||
else: print('.', end=' ')
|
print('=' * 50)
|
||||||
print()
|
|
||||||
print('=' * (lab.width * 2 + 4))
|
history = []
|
||||||
print(' S – вход E – выход # – стена . – пол')
|
|
||||||
|
while True:
|
||||||
|
cmd = input('\n Команда > ').lower().strip()
|
||||||
|
if cmd == 'q':
|
||||||
|
print('\n До свидания!')
|
||||||
|
break
|
||||||
|
elif cmd == 'b':
|
||||||
|
solver.set_strategy(BFS_Pathfinder())
|
||||||
|
stats = solver.solve()
|
||||||
|
print(f"\n BFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}")
|
||||||
|
elif cmd == 'd':
|
||||||
|
solver.set_strategy(DFS_Pathfinder())
|
||||||
|
stats = solver.solve()
|
||||||
|
print(f"\n DFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}")
|
||||||
|
elif cmd == 'a':
|
||||||
|
solver.set_strategy(AStar_Pathfinder())
|
||||||
|
stats = solver.solve()
|
||||||
|
print(f"\n A*: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}")
|
||||||
|
elif cmd in ('h','j','k','l'):
|
||||||
|
dirs = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)}
|
||||||
|
action = MoveAction(player, dirs[cmd], lab)
|
||||||
|
if action.execute():
|
||||||
|
history.append(action)
|
||||||
|
display.update('player_moved', lab)
|
||||||
|
if player.current == lab.exit:
|
||||||
|
print('\n ПОЗДРАВЛЯЕМ! ВЫ ВЫБРАЛИСЬ ИЗ ЛАБИРИНТА!')
|
||||||
|
print(f' Всего ходов: {len(history)}')
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('\n Там стена!')
|
||||||
|
elif cmd == 'u':
|
||||||
|
if history:
|
||||||
|
act = history.pop()
|
||||||
|
act.undo()
|
||||||
|
display.update('player_moved', lab)
|
||||||
|
print('\n Ход отменён')
|
||||||
|
else:
|
||||||
|
print('\n Нечего отменять')
|
||||||
|
else:
|
||||||
|
print('\n Неизвестная команда. Используйте H,J,K,L,U,Q,B,D,A')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
maze_file = sys.argv[1] if len(sys.argv) > 1 else 'maze1.txt'
|
maze_file = 'maze1.txt'
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
maze_file = sys.argv[1]
|
||||||
builder = TextLabyrinthBuilder()
|
builder = TextLabyrinthBuilder()
|
||||||
labyrinth = builder.build(maze_file)
|
labyrinth = builder.build(maze_file)
|
||||||
show_labyrinth(labyrinth)
|
run_interactive(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