Pull to refresh

Генерация лабиринтов алгоритмом Эйлера в 30 строк

Reading time5 min
Views25K
image
Ну сразу стоит извиниться за запоздалую реакцию, ведь «неделя 30 строк» прошла, а я выкладываю этот пост только сейчас. Все из-за тяжелой рабочей недели и только на выходных удалось выделить немного времени.
Сразу поблагодарю пользователя deadkrolik за статью Алгоритм Эллера для генерации лабиринтов и оговорюсь о том что я плут и мошенник и 30 строками кода тут и не пахнет))) Кто не любит ждать объяснений, прошу сразу на Fiddle.

Когда я только написал эти незадачливые лабиринтики, мой код занимал около 200 строк с комментариями, отступами и кучей ненужных переменных. Конечно же нужно было его сокращать. Но что-то задачка оказалась не из легких и я так и не смог сократить код 30 строк. Все что получилось после моих манипуляций можно глянуть здесь:
(function mapGen(elid, w, h, steps, complete) {
    var canvas = document.querySelector(elid), cell = canvas.getContext("2d");
    document.querySelector('#step').innerHTML = Math.floor(steps), document.querySelector('#complete').innerHTML = Math.floor(complete);
    canvas.width = w * 13 + 3, canvas.height = h * 13 + 3;
    cell.fillStyle = "black", cell.fillRect(0, 0, w * 13 + 3, h * 13 + 3);
    var line = new Array(w), cell_floor = new Array(w), cell_wall = new Array(w), many = 1;
    for (cr_l = 0; cr_l < h; cr_l++) {
        for (i = 0; i < w; i++) {
            if (cr_l == 0) line[i] = 0;
            cell.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 10), cell_wall[i] = 0;
            if (cell_floor[i] == 1) cell_floor[i] = line[i] = 0;
            if (line[i] == 0) line[i] = many++;}
        for (i = 0; i < w; i++) {
            cell_wall[i] = Math.floor(Math.random() * 2), cell_floor[i] = Math.floor(Math.random() * 2);
            if (((cell_wall[i] == 0) || (cr_l == h - 1)) && (i != w - 1) && (line[i + 1] != line[i])) {
                var temp_line = line[i + 1];
                for (j = 0; j < w; j++) if (line[j] == temp_line) line[j] = line[i];
                cell.clearRect(13 * i + 3, 13 * cr_l + 3, 15, 10);}
            if ((cr_l != h - 1) && (cell_floor[i] == 0)) cell.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 15);}
        for (i = 0; i < w; i++) {
            var count_floor = 0, count_hole = 0;
            for (j = 0; j < w; j++)
                if ((line[i] == line[j]) && (cell_floor[j] == 0)) count_hole++;
                else count_floor++;
            if (count_hole == 0) {
                cell_floor[i] = 0;
                cell.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 15);}}}
    cell.clearRect(13 * w, 3, 15, 10);
    var cur_x = 0, cur_y = 0;
    cell.fillStyle = "red";
    character(-1, -1);
    document.body.onkeydown = function (e) {
        if ((e.keyCode > 36) && (e.keyCode < 41)) character((e.keyCode - 38) % 2, (e.keyCode - 39) % 2);};
    function character(sx, sy) {
        var stepData = cell.getImageData(13 * cur_x + 7 + 6 * sx, 13 * cur_y + 7 + 6 * sy, 1, 1);
        if ((stepData.data[0] == 0) && (stepData.data[1] == 0) && (stepData.data[2] == 0) && (stepData.data[3] == 255)) sx = sy = 0;
        else document.querySelector('#step').innerHTML = Math.floor(document.querySelector('#step').innerHTML) + 1;
        cell.clearRect(13 * cur_x + 3, 13 * cur_y + 3, 10, 10);
        cur_x += sx, cur_y += sy;
        cell.fillRect(3 + 13 * cur_x, 3 + 13 * cur_y, 10, 10);
        if (cur_x >= w) mapGen("#canvas", w, h, 0, complete + 1);}
})("#canvas", 25, 30, 0, 0);


В общем зачете получилось 42 строки. Но даже здесь внимательные пользователи разглядят жульничество, т.к. я в одной строке объявлял несколько переменных, а так же в одну строку вмещал как условие, так и операторы. Уж очень хотелось все таки уложить все в 30 строк и я продолжил жульничество! В интернете нашел очень интересный и хороший оптимизатор кода, с которым вы можете познакомиться здесь. Ему удалось сократить мой код до 38 строк. Предела моей наглости же нету совсем и с помощью лихого оператора "," и нескольких нажатий клавиши DEL, я все таки вместил все в 30 строк.
(function mapGen(b, c, e, a, m) {
    function character(a, b) {
        var h = d.getImageData(13 * f + 7 + 6 * a, 13 * g + 7 + 6 * b, 1, 1);
        0 == h.data[0] && 0 == h.data[1] && 0 == h.data[2] && 255 == h.data[3] ? a = b = 0 : document.querySelector("#step").innerHTML = Math.floor(document.querySelector("#step").innerHTML) + 1;
        d.clearRect(13 * f + 3, 13 * g + 3, 10, 10), f += a, g += b, d.fillRect(3 + 13 * f, 3 + 13 * g, 10, 10);
        f >= c && mapGen("#canvas", c, e, 0, m + 1)}
    b = document.querySelector(b);
    var d = b.getContext("2d");
    document.querySelector("#step").innerHTML = Math.floor(a), document.querySelector("#complete").innerHTML = Math.floor(m);
    b.width = 13 * c + 3, b.height = 13 * e + 3, d.fillStyle = "black", d.fillRect(0, 0, 13 * c + 3, 13 * e + 3), a = Array(c), b = Array(c);
    var k = Array(c), q = 1;
    for (cr_l = 0; cr_l < e; cr_l++) {
        for (i = 0; i < c; i++) 0 == cr_l && (a[i] = 0), d.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 10), k[i] = 0, 1 == b[i] && (b[i] = a[i] = 0), 0 == a[i] && (a[i] = q++);
        for (i = 0; i < c; i++) {
            k[i] = Math.floor(2 * Math.random()), b[i] = Math.floor(2 * Math.random());
            if ((0 == k[i] || cr_l == e - 1) && i != c - 1 && a[i + 1] != a[i]) {
                var l = a[i + 1];
                for (j = 0; j < c; j++) a[j] == l && (a[j] = a[i]);
                d.clearRect(13 * i + 3, 13 * cr_l + 3, 15, 10) }
            cr_l != e - 1 && 0 == b[i] && d.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 15) }
        for (i = 0; i < c; i++) {
            var p = l = 0;
            for (j = 0; j < c; j++) a[i] == a[j] && 0 == b[j] ? p++ : l++;
            0 == p && (b[i] = 0, d.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 15)) } }
    d.clearRect(13 * c, 3, 15, 10);
    var f = 0, g = 0;
    d.fillStyle = "red", character(-1, -1);
    document.body.onkeydown = function (a) {
        36 < a.keyCode && 41 > a.keyCode && character((a.keyCode - 38) % 2, (a.keyCode - 39) % 2) }
})("#canvas", 25, 30, 0, 0);


Входными параметрами этой функции являются область рисования, ширина и длинна лабиринта по количеству ячеек, количество шагов, сделанных персонажем и количество пройденных лабиринтов.
Не претендую на хороший код или хотя бы хорошие отзывы, но мне приятно поделиться своим первым постом. Прошу строго не судить. И все замечания и исправления внимательно выслушаю в комментариях.

UPD 1:
Для повышения эстетики и читаемости кода:
(function mapGen(b, c, e, a, m) {
    // Функция управления персонажем
    function character(a, b) {
        // Получаем цвет пикселя из промежутка между текущей ячейкой и той, в сторону которой персонаж должен передвинуться
        var h = d.getImageData(13 * f + 7 + 6 * a, 13 * g + 7 + 6 * b, 1, 1);
        // Если цвет пикселя черный, то не перемещаем персонажа (обнуляем dx (a) и dy (b)), иначе увеличиваем количество шагов
        0 == h.data[0] && 0 == h.data[1] && 0 == h.data[2] && 255 == h.data[3] ? a = b = 0 : document.querySelector("#step").innerHTML = Math.floor(document.querySelector("#step").innerHTML) + 1;
        // Закрашиваем персонажа
        d.clearRect(13 * f + 3, 13 * g + 3, 10, 10); 
        // Меняем его текушие координаты
        f += a; 
        g += b; 
        // Вновь отрисовываем его
        d.fillRect(3 + 13 * f, 3 + 13 * g, 10, 10);
        // Если персонаж вышел за пределы лабиринта, то генерируем новый лабиринт и начинаем игру сначала
        f >= c && mapGen("#canvas", c, e, 0, m + 1)
    }

    // Выбираем область рисования
    b = document.querySelector(b);
    var d = b.getContext("2d");
    // И вписываем количество шагов и пройденных лабиринтов
    document.querySelector("#step").innerHTML = Math.floor(a);
    document.querySelector("#complete").innerHTML = Math.floor(m);
    // Зададим ширину и высоту области лабиринта
    b.width = 13 * c + 3;
    b.height = 13 * e + 3;
    // И закрасим в черный цвет
    d.fillStyle = "black";
    d.fillRect(0, 0, 13 * c + 3, 13 * e + 3);
    
    // Объявим массивы для хранения значения множества текущей ячейки, для значения стенки справа и для значения стенки снизу
    a = Array(c); 
    b = Array(c);
    var k = Array(c),
        // Текущее множество
        q = 1;

    // Цикл по строкам
    for (cr_l = 0; cr_l < e; cr_l++) {
        // Проверка принадлежности ячейки в строке к какому-либо множеству        
        for (i = 0; i < c; i++) 
            0 == cr_l && (a[i] = 0), d.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 10), k[i] = 0, 1 == b[i] && (b[i] = a[i] = 0), 0 == a[i] && (a[i] = q++);

        // Создание случайным образом стенок справа и снизу
        for (i = 0; i < c; i++) {
            k[i] = Math.floor(2 * Math.random()), b[i] = Math.floor(2 * Math.random());
            
            if ((0 == k[i] || cr_l == e - 1) && i != c - 1 && a[i + 1] != a[i]) {
                var l = a[i + 1];
                for (j = 0; j < c; j++) a[j] == l && (a[j] = a[i]);
                d.clearRect(13 * i + 3, 13 * cr_l + 3, 15, 10)
            }
            cr_l != e - 1 && 0 == b[i] && d.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 15)
        }

        // Проверка на замкнутые области.
        for (i = 0; i < c; i++) {
            var p = l = 0;
            for (j = 0; j < c; j++) a[i] == a[j] && 0 == b[j] ? p++ : l++;
            0 == p && (b[i] = 0, d.clearRect(13 * i + 3, 13 * cr_l + 3, 10, 15))
        }
    }

    // Рисуем выход из лабиринта
    d.clearRect(13 * c, 3, 15, 10);
    // Обнуляем текущие координаты персонажа
    var f = 0,
        g = 0;
    // Задаем крассный цвет
    d.fillStyle = "red";
    // И ставим персонажа в начало лабиринта
    character(-1, -1);
    // Ожидаем нажатия стрелок
    document.body.onkeydown = function (a) {
        36 < a.keyCode && 41 > a.keyCode && character((a.keyCode - 38) % 2, (a.keyCode - 39) % 2)
    }
})("#canvas", 25, 30, 0, 0);
Tags:
Hubs:
Total votes 45: ↑18 and ↓27-9
Comments5

Articles