Графы
8.6. Лабиринт
В настоящем параграфе строится лабиринт. Стенки лабиринта генерируются случайным образом. В качестве места для размещения лабиринта используется клеточное поле размером (23 ряда и 38 столбцов). Размер поля подобран так, чтобы его было удобно отображать в окне консоли (рис. 8.5). Программа ищет путь из левого верхнего угла поля в правый нижний угол. Крайние столбцы слева и справа, а также самый нижний ряд поля всегда свободны, так что по этим клеткам можно перемещаться. Этим с избытком обеспечивается существование пути из начальной клетки в конечную. Для всех остальных клеток при генерации лабиринта шанс стать "стенкой", т. е. клеткой, по которой перемещаться нельзя, равен одному из трех.
Для поиска пути выхода из лабиринта используется "жадный" алгоритм. Первым продлевается путь, последняя вершина которого находится ближе всего к конечной клетке относительно обычного расстояния, без учета стенок. Для ускорения поиска используется следующее ограничение: пути не продлеваются в клетки, из которых уже строились продолжения путей. В результате путь находится быстро, но, в общем случае, этот путь не является кратчайшим.
Для клеток, по которым можно перемещаться, используется серый цвет, клетки стенок лабиринта желтого цвета. Путь прокрашивается синим цветом. Перед выводом каждой клетки пути делается небольшая пауза6Пример приведен для версии 7.5. В версии 7.4 вместо выражения not(A in B) должно быть not(list::isMember(A, B)). Вместо A и B в листингах стоят разные переменные..
open core, console, console_native, list, math, programControl domains state = empty; wall; pathcell. square = sq(unsigned16, unsigned16). class facts m : unsigned16 := 23. n : unsigned16 := getConsoleWidth() div 2 - 2. class facts cell: (unsigned16, unsigned16, state). class predicates create: (). print: (). f: (unsigned) -> state. attr: (state) -> unsigned16. printCell: (square, state). clauses create():- % генерация лабиринта I = std::fromTo(0, m - 1), J = std::fromTo(0, n - 1), X = if (J = 0; J = n - 1; I = m - 1), ! then 1 else random(3) end if, assert(cell(I, J, f(X))), fail; succeed(). f(0) = wall:- !. f(_) = empty. attr(wall) = bit::bitLeft(14, 4):- !. attr(empty) = bit::bitLeft(7, 4):- !. attr(_) = bit::bitLeft(9, 4). print():- % печать лабиринта cell(I, J, State), printCell(sq(I, J), State), fail; succeed(). printCell(sq(I, J), State):- setTextAttribute(attr(State)), setLocation(coord(2 * J + 2, I + 1)), write(" "). % два пробельных символа class predicates search: (square, square) -> square*. path: (square**, square*, square) -> square* determ. next: (square) -> square nondeterm. isEmpty: (square) determ. d: (square*, square) -> real. clauses search(Start, Goal) = reverse(Path):- Path = path([[Start]], [], Goal), !. search(Start, _) = [Start]. path([[Goal | Path] | _], _, Goal) = [Goal | Path]:- !. path([[Cell | Path] | PathList], CList, Goal) = path(NewPathList, [Cell | CList], Goal):- NextPaths = [[NextCell, Cell | Path] || NextCell = next(Cell), isEmpty(NextCell), not(NextCell in CList)], if NextPaths <> [] then NextPathList = append(NextPaths, PathList), BestPath = minimumBy( {(P1, P2) = compare(d(P1, Goal), d(P2, Goal))}, NextPathList), NewPathList = [BestPath | remove(NextPathList, BestPath)] else NewPathList = PathList end if. next(sq(I, J)) = sq(I, J + 1):- J < n - 1. next(sq(I, J)) = sq(I + 1, J):- I < m - 1. next(sq(I, J)) = sq(I - 1, J):- I > 0. next(sq(I, J)) = sq(I, J - 1):- J > 0. isEmpty(sq(I, J)):- cell(I, J, empty), !. d([sq(I1, J1) | _], sq(I2, J2)) = abs(I2 - I1)^2 + abs(J2 - J1)^2:- !. d(_, _) = 0. run():- setConsoleTitle("Лабиринт"), create(), print(), Path = search(sq(0, 0), sq(m - 1, n - 1)), forAll(Path, {(Cell):- printCell(Cell, pathcell), sleep(50)}), _ = readLine().Пример 8.8. Поиск пути в лабиринте
Предикат getConsoleWidth возвращает ширину окна консоли. Предикат abs находит абсолютную величину числа.
Вместо обычного расстояния можно использовать манхэттенское расстояние. Для этого нужно определить предикат d/2 следующим образом:
d([sq(I1, J1) | _], sq(I2, J2)) = abs(I2 - I1) + abs(J2 - J1):- !. d(_, _) = 0.
Упражнения
- Найдите пути, проходящие только через разрешенное множество вершин.
- Найдите пути между заданными вершинами, не проходящие через запрещенное множество вершин.
- Найдите пути между заданными вершинами, состоящие из заданного количества ребер.
- Реализуйте алгоритм поиска путей в ширину для графа, представленного в программе в виде фактов, каждый из которых хранит вершину и список пар, содержащих смежную вершину и вес ребра, который их соединяет.
- Найдите эйлеров цикл в графе.
- Используйте эвристический поиск в глубину для решения задачи о коммивояжере — для поиска гамильтоновых циклов в графе минимального суммарного веса.
- Определите предикат, который возвращает элементы матрицы смежности графа.
- Сгенерируйте полный граф с заданным количеством вершин и случайными весами ребер.
- Используйте алгоритм "первый лучший" для поиска кратчайшего по стоимости пути между фигурами, стоящими на клетчатом поле, клетки которого имеют разный вес (от 1 до 4). Вес клеток генерируется случайным образом. Как поле, так и пути должны отображаться в окне консоли.