Обход препятствий: волновой алгоритм (Алгоритм Ли) 8 клеточный
Обновлено:
В этой статье

В прошлой статье описывал как обходить препятствия, используя волновой алгоритм (Алгоритм Ли). Но там лишь 4 клеточная реализация была. Теперь же сделал, проверяя все 8 клеток, ну и оптимизировал немного.
Волновой алгоритм (Алгоритм Ли), использующий 8 клеток
Добавить проверки ещё 4 клеток, по сути, не так сложно. После этого ещё необходимо рассмотреть нюанс с диагональными путями.

Очевидно, что на самом деле невозможно пройти из 2.6 в 4.2, хотя этот путь и короче с точки зрения алгоритма. Необходимо ещё добавить проверку на препятствия смежных клеток при движении по диагонали.
Если эту проверку сделать, то путь будет 2.6 -> 3.6 -> 4.2. К слову, диагональный путь у меня имеет длину 1.6. С точки зрения математики то он будет корень из 2. По факту же, числа можно любые ставить в клетки. К примеру, если проходимость у них разная, алгоритм будет выбирать клетки, с более лучшей.
Дёрганья персонажа в сложной ситуации
Кто играл в StarCraft, в курсе, что это значит. Когда вы кардинально маршрут меняете, то юниты дёргаются. Это является следствием одного нелостатка алгоритма (в то же время и плюса).
Чтобы использовать алгоритм, необходимо карту поделить на клетки. И необходимо отдавать себе отчёт, что слишком большие клетки не прокатят, так же как и слишком мелкие. В том же StarCraft'e клетки по ширине поболее 50 пикселей. Поэтому то и возникают дёрганья. Если бы клетки были размером 1 px, то было бы плавно, но тогда бы увеличилось время на просчёт маршрутов. И вы учтите, что считает то он не только, когда вы кликнули на область и послали юнитов. Сам компьютер для ИИ постоянно пути просчитывает.
В StarCraft самая большая карта имеет размер 200x200 клеток. Если проверить мой алгоритм при таком размере, то вычисления занимают 0.03 секунды. То есть, если вы неустанно кликаете, что-то типо 5 нажатий в минуту, то только на расчёт путей для вас занимает 0.15 сек. Это без учёта маршрутов ИИ. Естественно на C++ будет быстрее, чем на js, и там разные методы оптимизации. Быть может запоминаются пройденные уже пути...не знаю.
Волновой алгоритм (Алгоритм Ли), использующий 8 клеток на JavaScript
Весь алгоритм являет собой 1 функцию, по сути. На входе точки текущей волны.
this.checkPoints= function(p)
{
var count = p.length;
points = new Array();
//если закончен расчёт, тикаем
if(count==0 || bEnd) return;
//обходим точки
for(i=0;i<count;++i
{
//если достигли конца, то тикаем
if(p[i].i == endPoint.i && p[i].j == endPoint.j)
{
bEnd = true;
return;
}
//var x = 0;
//var y = 0;
//проверяем окружные 8 клеток
for( y=-1;y<=1;++y)
for( x=-1;x<=1;++x)
if(!(x==0&&y==0))
//проверка на выход за пределы поля
if(checkPointLimit(p[i].i+y,p[i].j+x))
if(mas[p[i].i+y][p[i].j+x] ==0)
//проверка на препятствия
if(checkPointObstacle(p[i].i+y, p[i].j+x,p[i].i, p[i].j))
//проверка значения
if(checkPointValue (p[i].i+y,p[i].j+x, mas[p[i].i][p[i].j]+((Math.abs(x)==Math.abs(y))?1.6:1), points))
//если надо, рисуем волны
if(showPriority && typeOfDrawing == 1)
{
var ar = new Array();
ar[0]={i:p[i].i+y, j:p[i].j+x};
delay+=10;
timer = setTimeout( function(ar ) {return function(){self.drawPointsAsynchron(ar)}}(ar), delay);
}
}
//повторяем для новых клеток
this.checkPoints(points);
}
HTML5, Canvas, слои
При каждом клике, необходимо canvas перерисовывать. То есть, либо полностью, либо только те клетки, что были изменены. Первый вариант сразу отпадает, так как, к примеру, при размере 200x200 клеток, на отрисовку будет тратиться намного больше времени, чем на вычисления.
Второй вариант тоже не очень, ибо сложно и муторно следить за клетками, которые изменились. По этому, лучший вариант - использовать слои. В нижнем слое будет фон, то есть неизменная часть, а волны и пути будут в верхнем слое. Поэтому при клике, стираем этой верхний слой и рисуем лишь путь. Тогда время на прорисовку соизмеримо со временем вычисления. В сущности же, при большом поле (более 300x300 клеток) время затраченное на вычисления уже больше, чем время на прорисовку.
То есть, у нас есть два canvas (дин под другим) без фона (прозрачные). Вот и вся магия (:
Как это всё выглядит, можно увидеть ниже. Кликаем на нужную клетку, алгоритм находит оптимальный путь.
Если выбрать оптимальный размер клеток, скажем как в том же StarCraft, то можно запоминать уже найденные пути. Это одномерный массив, на C++ для этого удобно было бы использовать стек. Память это жрать будет не так много, выборка из стека довольно быстро осуществляется. Так что, по сути, ближе к концу игры практически не надо будет считать пути, а брать из кеша уже просчитанные. Всё упирается тогда в память. По сути, можно при загрузке карты просчитать пути для каждой точки и это дерево хранить в памяти 0___0. Ну, это конечно, ооочень не оптимально)
Кому интересно, можете покопаться в исходнике.