Создание лабиринта
Наш лабиринт будет в виде матрицы размером n*m
с нулями для проходов и единицами для стен.
a = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
И ещё мы установим точки старта и финиша:
start = 1, 1
end = 2, 5
Итак, лабиринт готов, смотрите:
Алгоритм
Алгоритм для прохождения по лабиринту следующий:
- Создаём нулевую матрицу подходящего размера.
- Ставим
1
в точку старта. - Во все позиции вокруг
1
ставим2
, если нет стены. - Вокруг двоек ставим тройки (
3
). Тоже, если нет стены. - И так далее…
- Как только ставим цифру в точку финиша, останавливаемся. Это число и является минимальной длиной пути.
Создаём матрицу
Это просто. Возможно, есть функция по типу нулей
или что-то подобное, но я знаю только один способ. Итак, создаем матрицу вручную и устанавливаем точку старта:
m = []
for i in range(len(a)):
m.append([])
for j in range(len(a[i])):
m[-1].append(0)
i,j = start
m[i][j] = 1
Получаем следующую матрицу:
0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
Рассчитываем шаг
Теперь создадим функцию только для одного шага:
def make_step(k):
for i in range(len(m)):
for j in range(len(m[i])):
if m[i][j] == k:
if i>0 and m[i-1][j] == 0 and a[i-1][j] == 0:
m[i-1][j] = k + 1
if j>0 and m[i][j-1] == 0 and a[i][j-1] == 0:
m[i][j-1] = k + 1
if i<len(m)-1 and m[i+1][j] == 0 and a[i+1][j] == 0:
m[i+1][j] = k + 1
if j<len(m[i])-1 and m[i][j+1] == 0 and a[i][j+1] == 0:
m[i][j+1] = k + 1
Она принимает число шагов k
в качестве аргумента. Функция выполняет очень простые вещи:
- Сканирует матрицу при помощи цикла for.
- Если находится число, которое соответствует количеству шагов
k
, смотрим на ячейки вокруг и проверяем:
1. Нет ли здесь пока еще числа.
2. Нет ли здесь стены.
И ставимk+1
этим ячейкам.
Если запустить эту функцию 8 раз, получится вот что:
make_step(1)
make_step(2)
make_step(3)
make_step(4)
make_step(5)
make_step(6)
make_step(7)
make_step(8)
Получаем следующую матрицу:
0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 2 0 0 0 0 0 0 0 0
0 3 0 0 0 0 0 0 0 0
0 4 0 0 0 0 0 0 0 0
0 5 0 9 0 0 0 0 0 0
0 6 7 8 9 0 0 0 0 0
0 7 0 9 0 0 0 0 0 0
0 8 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
Она соответствует такому изображению:
Давайте продолжим, как и делали, пока не будет заполнена точка финиша:
k = 0
while m[end[0]][end[1]] == 0:
k += 1
make_step(k)
Теперь у нас будет вот такая матрица:
0 0 0 0 0 0 0 0 0 0
0 1 0 13 0 0 22 21 20 0
0 2 0 12 0 22 21 20 19 0
0 3 0 11 0 0 0 0 18 0
0 4 0 10 11 12 13 0 17 0
0 5 0 9 10 11 12 0 16 0
0 6 7 8 9 10 11 0 15 0
0 7 0 9 10 11 12 13 14 0
0 8 0 10 11 12 13 14 15 0
0 0 0 0 0 0 0 0 0 0
Лабиринт по ней будет выглядеть вот так:
Что такое путь?
Задача теперь — найти кратчайший путь по этой матрице.
Шаги для решения следующие:
- Пойти в точку финиша, допустим, число здесь —
k.
- Найти соседнюю ячейку со значением
k-1
, пойти туда, уменьшитьk
на единицу. - Повторить предыдущий шаг, пока не доберемся до начальной точки, а именно:
k=1.
i, j = end
k = m[i][j]
the_path = [(i,j)]
while k > 1:
if i > 0 and m[i - 1][j] == k-1:
i, j = i-1, j
the_path.append((i, j))
k-=1
elif j > 0 and m[i][j - 1] == k-1:
i, j = i, j-1
the_path.append((i, j))
k-=1
elif i < len(m) - 1 and m[i + 1][j] == k-1:
i, j = i+1, j
the_path.append((i, j))
k-=1
elif j < len(m[i]) - 1 and m[i][j + 1] == k-1:
i, j = i, j+1
the_path.append((i, j))
k -= 1
И теперь у нас есть координаты пути:
[(2, 5), (2, 6), (2, 7), (2, 8), (3, 8), (4, 8), (5, 8), (6, 8), (7, 8), (7, 7), (7, 6), (6, 6), (6, 5), (6, 4), (6, 3), (6, 2), (6, 1), (5, 1), (4, 1), (3, 1), (2, 1), (1, 1)]
И вот как это выглядит:
Лабиринт посложнее
Теперь попробуем, как сработает наш алгоритм в других лабиринтах.
Для начала немного видоизменим лабиринт:
И потом сделаем его больше:
Надеюсь, вам понравилось!
Чтобы визуализировать самостоятельно, как на картинках выше, установите библиотеку Pillow
и вперёд:
from PIL import Image, ImageDraw
images = []
a = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0 ,0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 ,0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0 ,0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0 ,0, 0, 0, 1, 0, 1, 1, 1, 1],
[1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0 ,0, 0, 0, 1, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0 ,0, 0, 0, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 ,0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 ,0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]
zoom = 20
borders = 6
start = 1,1
end = 5,19
def make_step(k):
for i in range(len(m)):
for j in range(len(m[i])):
if m[i][j] == k:
if i>0 and m[i-1][j] == 0 and a[i-1][j] == 0:
m[i-1][j] = k + 1
if j>0 and m[i][j-1] == 0 and a[i][j-1] == 0:
m[i][j-1] = k + 1
if i<len(m)-1 and m[i+1][j] == 0 and a[i+1][j] == 0:
m[i+1][j] = k + 1
if j<len(m[i])-1 and m[i][j+1] == 0 and a[i][j+1] == 0:
m[i][j+1] = k + 1
def print_m(m):
for i in range(len(m)):
for j in range(len(m[i])):
print( str(m[i][j]).ljust(2),end=' ')
print()
def draw_matrix(a,m, the_path = []):
im = Image.new('RGB', (zoom * len(a[0]), zoom * len(a)), (255, 255, 255))
draw = ImageDraw.Draw(im)
for i in range(len(a)):
for j in range(len(a[i])):
color = (255, 255, 255)
r = 0
if a[i][j] == 1:
color = (0, 0, 0)
if i == start[0] and j == start[1]:
color = (0, 255, 0)
r = borders
if i == end[0] and j == end[1]:
color = (0, 255, 0)
r = borders
draw.rectangle((j*zoom+r, i*zoom+r, j*zoom+zoom-r-1, i*zoom+zoom-r-1), fill=color)
if m[i][j] > 0:
r = borders
draw.ellipse((j * zoom + r, i * zoom + r, j * zoom + zoom - r - 1, i * zoom + zoom - r - 1),
fill=(255,0,0))
for u in range(len(the_path)-1):
y = the_path[u][0]*zoom + int(zoom/2)
x = the_path[u][1]*zoom + int(zoom/2)
y1 = the_path[u+1][0]*zoom + int(zoom/2)
x1 = the_path[u+1][1]*zoom + int(zoom/2)
draw.line((x,y,x1,y1), fill=(255, 0,0), width=5)
draw.rectangle((0, 0, zoom * len(a[0]), zoom * len(a)), outline=(0,255,0), width=2)
images.append(im)
m = []
for i in range(len(a)):
m.append([])
for j in range(len(a[i])):
m[-1].append(0)
i,j = start
m[i][j] = 1
k = 0
while m[end[0]][end[1]] == 0:
k += 1
make_step(k)
draw_matrix(a, m)
i, j = end
k = m[i][j]
the_path = [(i,j)]
while k > 1:
if i > 0 and m[i - 1][j] == k-1:
i, j = i-1, j
the_path.append((i, j))
k-=1
elif j > 0 and m[i][j - 1] == k-1:
i, j = i, j-1
the_path.append((i, j))
k-=1
elif i < len(m) - 1 and m[i + 1][j] == k-1:
i, j = i+1, j
the_path.append((i, j))
k-=1
elif j < len(m[i]) - 1 and m[i][j + 1] == k-1:
i, j = i, j+1
the_path.append((i, j))
k -= 1
draw_matrix(a, m, the_path)
for i in range(10):
if i % 2 == 0:
draw_matrix(a, m, the_path)
else:
draw_matrix(a, m)
print_m(m)
print(the_path)
images[0].save('maze.gif',
save_all=True, append_images=images[1:],
optimize=False, duration=1, loop=0)
Я не планировал делиться подробным кодом, так что, возможно, всё выглядит немного неясно. Идея была в том, чтобы визуализировать алгоритм, не показывая, как именно это было сделано 🙂
Читайте также:
- 10 самых продуктивных техник для работы с файлами в Python
- Суть 4 хитроумных концепций Python для новичков
- 10 идиоматических приемов для эффективного программирования на Python
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Dr Pommes: Solve a Maze with Python