[2] add A*

This commit is contained in:
YaroslavtsevAS 2026-05-24 15:34:20 +00:00
parent 1dcc56d0bc
commit fc6b64b7cd

View File

@ -1,6 +1,8 @@
import sys
import os
import time
from collections import deque
import heapq
class Tile:
def __init__(self, x, y):
@ -109,12 +111,12 @@ class TextLabyrinthBuilder(LabyrinthBuilder):
class Pathfinder:
def find_path(self, lab, start, goal): raise NotImplementedError
def _build_path(self, predecessors, start, goal):
def _build_path(self, preds, start, goal):
path = []
cur = goal
while cur is not None:
path.append(cur)
cur = predecessors.get(cur)
cur = preds.get(cur)
path.reverse()
return path
@ -161,10 +163,170 @@ class DFS_Pathfinder(Pathfinder):
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:
def __init__(self, lab):
self._lab = lab
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):
self._strategy = strategy
@ -172,50 +334,74 @@ class LabyrinthSolver:
def solve(self):
if self._strategy is None:
return None
start_t = time.perf_counter()
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):
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 run_interactive(lab):
player = Explorer(lab.start, lab)
display = TerminalDisplay(player)
display.update('labyrinth_loaded', lab)
solver = LabyrinthSolver(lab)
solver.attach(display)
print('\n УПРАВЛЕНИЕ:')
print(' H влево J вниз K вверх L вправо')
print(' U отменить ход Q выход')
print(' Автопоиск: B BFS D DFS A A*')
print('=' * 50)
history = []
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__':
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()
labyrinth = builder.build(maze_file)
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("Путь не найден!")
run_interactive(labyrinth)