В прошлый раз мы сделали генератор лабиринтов на JavaScript и показали результат в консоли браузера. В нём можно было разглядеть лабиринт и найти свободные пути, но такое решение выглядит не очень красиво. Сегодня нарисуем лабиринт красиво — на странице, используя холст и новый скрипт.
Что делаем
У нас уже есть скрипт, который составляет карту лабиринта. Теперь нам нужно нарисовать её на странице, чтобы можно было распечатать и отдать детям или пройти лабиринт самому.
Для этого мы добавим на страницу новый скрипт, который возьмёт готовую карту лабиринта, обработает её и покажет красивый результат в браузере.
Логика нового скрипта будет такой:
- Прописываем все необходимые переменные, которые нам понадобятся.
- Нарисуем на холсте рамку по периметру — это будет внешняя граница лабиринта.
- По очереди проходим по карте лабиринта и закрашиваем чёрным те ячейки, которые на карте помечены как стены.
Всё это сделаем в новом скрипте drawMaze.js.
Переменные
Чтобы было удобнее управлять всеми параметрами лабиринта из одного места, добавим в этот скрипт переменные, которые отвечают за количество клеток в лабиринте по вертикали и горизонтали. Ещё нам понадобится размер каждой клетки, чтобы знать, какого размера лабиринт у нас будет на экране. Финальный штрих — добавляем внешнюю границу лабиринта и тоже пишем её размер.
Также в этом блоке подключим холст — найдём его по тегу и сразу зададим нужные параметры.
// количество колонок в лабиринте
const columnsSize = 15;
// количество строк в лабиринте
const rowsSize = 15;
// размер клетки в лабиринте
const fieldSize = 7;
// рамка (внешняя граница лабиринта)
const padding = 10;
// находим холст на странице по имени элемента
const canvas = document.querySelector('canvas');
// создаём переменную, через которую будем работать с холстом
const context = canvas.getContext('2d');
// создаём новую карту лабиринта, которую будем отрисовывать
const map = generateMaze(columnsSize, rowsSize);
Подготавливаем холст
Теперь у нас есть всё, чтобы начать рисовать. Вот что нам нужно сделать на этом этапе:
- Посчитать и установить фактические размеры холста в пикселях.
- Закрасить весь холст чёрным.
- Отступить на расстояние рамки от каждого угла и нарисовать белый прямоугольник внутри.
У нас получится на холсте чёрная рамка, внутри которой ничего нет. Это нормально — стены мы добавим на следующем шаге.
// рисуем рамку и готовимся к отрисовке лабиринта
function init () {
// устанавливаем размеры холста
canvas.width = padding * 2 + columnsSize * fieldSize;
canvas.height = padding * 2 + rowsSize * fieldSize;
// цвет заливки
context.fillStyle = 'black';
// рисуем прямоугольник на весь холст с координатами левого верхнего и правого нижнего углов
context.rect(0, 0, canvas.width, canvas.height);
// закрашиваем его выбранным цветом
context.fill();
// делаем белое поле внутри рамки, чтобы потом нарисовать на нём стены
context.fillStyle = 'white';
// сообщаем скрипту, что сейчас будем рисовать новую фигуру
context.beginPath();
// рисуем прямоугольник, отступив от границ холста на толщину рамки
context.rect(padding, padding, canvas.width - padding * 2, canvas.height - padding * 2);
// закрашиваем его белым
context.fill();
}
Рисуем стены
Нам осталось нарисовать стены на холсте, постоянно сверяясь с картой лабиринта. Мы будем каждый раз брать новые координаты лабиринта, смотреть, что там лежит, и, если там стена, — рисуем на этом месте чёрную клетку.
Получать значения ячейки из переменной с лабиринтом мы будем тем же способом, что и в прошлый раз: напишем отдельную функцию. Её задачей будет проверить координаты на правильность: если мы запросим значение ячейки с неверными координатами, то у нас программа не остановится, а просто проигнорирует этот запрос.
// получаем значение ячейки из лабиринта
function getField (x, y) {
// если хотя бы одна из координат не находится в границах карты
if (x < 0 || x >= columnsSize || y < 0 || y >= rowsSize) {
// выходим из функции и говорим, что такой ячейки нет
return null;
}
// если дошли досюда, значит, координата верная, и мы возвращаем её значение из карты лабиринта
return map[y][x];
}
Теперь рисуем стены так: по очереди переберём все координаты карты с лабиринтом и те клетки, которые помечены как стена, на холсте нарисуем чёрными.
// отрисовываем карту
function drawMap () {
// обрабатываем по очереди все ячейки в каждом столбце и строке
for (let x = 0; x < columnsSize; x++) {
for (let y = 0; y < rowsSize; y++) {
// если на карте лабиринта эта ячейка помечена как стена
if (getField(x, y) === '▉') {
// берём чёрный цвет
context.fillStyle = 'black';
// начинаем рисовать новую фигуру
context.beginPath();
// делаем прямоугольник на месте этой ячейки
context.rect(padding + x * fieldSize, padding + y * fieldSize, fieldSize, fieldSize);
// закрашиваем его чёрным
context.fill();
}
}
}
}
Запускаем скрипт
Для запуска нам нужно добавить вызов функций init() и drawMap() в самый конец файла:
// рисуем рамку и готовимся к отрисовке лабиринта
init();
// рисуем лабиринт
drawMap();
Чтобы скрипт сработал, добавим его в наш исходный HTML-файл:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Лабиринт</title>
</head>
<body>
<!-- подготавливаем пустой холст, чтобы работать с ним из скрипта -->
<canvas></canvas>
<!-- скрипт, который создаёт лабиринт -->
<script src="generateMaze.js"> </script>
<!-- этот скрипт отвечает за отрисовку лабиринта -->
<script src="drawMaze.js"></script>
</body>
</html>
Посмотреть лабиринт на странице проекта.
// количество колонок в лабиринте
const columnsSize = 51;
// количество строк в лабиринте
const rowsSize = 51;
// размер клетки в лабиринте
const fieldSize = 7;
// рамка (внешняя граница лабиринта)
const padding = 10;
// находим холст на странице по имени элемента
const canvas = document.querySelector('canvas');
// создаём переменную, через которую будем работать с холстом
const context = canvas.getContext('2d');
// создаём новую карту лабиринта, которую будем отрисовывать
const map = generateMaze(columnsSize, rowsSize);
// рисуем рамку и готовимся к отрисовке лабиринта
function init () {
// устанавливаем размеры холста
canvas.width = padding * 2 + columnsSize * fieldSize;
canvas.height = padding * 2 + rowsSize * fieldSize;
// цвет заливки
context.fillStyle = 'black';
// рисуем прямоугольник на весь холст с координатами левого верхнего и правого нижнего углов
context.rect(0, 0, canvas.width, canvas.height);
// закрашиваем его выбранным цветом
context.fill();
// делаем белое поле внутри рамки, чтобы потом нарисовать на нём стены
context.fillStyle = 'white';
// сообщаем скрипту, что сейчас будем рисовать новую фигуру
context.beginPath();
// рисуем прямоугольник, отступив от границ холста на толщину рамки
context.rect(padding, padding, canvas.width - padding * 2, canvas.height - padding * 2);
// закрашиваем его белым
context.fill();
}
// получаем значение ячейки из лабиринта
function getField (x, y) {
// если хотя бы одна из координат не находится в границах карты
if (x < 0 || x >= columnsSize || y < 0 || y >= rowsSize) {
// выходим из функции и говорим, что такой ячейки нет
return null;
}
// если дошли досюда, значит, координата верная, и мы возвращаем её значение из карты лабиринта
return map[y][x];
}
// отрисовываем карту
function drawMap () {
// обрабатываем по очереди все ячейки в каждом столбце и строке
for (let x = 0; x < columnsSize; x++) {
for (let y = 0; y < rowsSize; y++) {
// если на карте лабиринта эта ячейка помечена как стена
if (getField(x, y) === '▉') {
// берём чёрный цвет
context.fillStyle = 'black';
// начинаем рисовать новую фигуру
context.beginPath();
// делаем прямоугольник на месте этой ячейки
context.rect(padding + x * fieldSize, padding + y * fieldSize, fieldSize, fieldSize);
// закрашиваем его чёрным
context.fill();
}
}
}
}
// рисуем рамку и готовимся к отрисовке лабиринта
init();
// рисуем лабиринт
drawMap();
Что дальше
У нашего лабиринта есть две маленькие проблемы:
- Если число клеток по горизонтали и вертикали больше 150, страница грузится очень долго. Всё потому, что у нас только один трактор, который чистит лабиринт.
- Непонятно, откуда начинать и куда идти, чтобы пройти лабиринт.
Исправим эти недочёты в следующей версии. Потом будет ещё одно продолжение этого же проекта.