Подтвердить что ты не робот

Как выпрямить ненужные витки в результатах поиска графика A *?

Я работаю над реализацией JavaScript раннего 90 приключенческих игр и, в частности, прокладывая путь от места, где герой стоит местоположение, на которое игрок нажал. Мой подход состоит в том, чтобы сначала определить, может ли быть проведена линия пролива (без препятствий), если нет, то искать путь четких путевых точек, используя Брайан Гринстед отлично javascript-astar. Проблема, с которой я столкнулась, - это путь (в то время как оптимальный будет поворачиваться в пространства, которые кажутся пользователю непреднамеренным. Вот классический пример того, что я говорю (зеленый путь - это сгенерированный путь, красные точки каждый из них поворачивается, где изменяется направление пути):

The green path is the hero, the red dots are the turns

Теперь я знаю, что A * гарантированно возвратит путь, который не может быть проще (с точки зрения шагов), но я изо всех сил пытаюсь реализовать эвристику, которая поворачивает грузы. Вот изображение, в котором показаны два других пути, которые также квалифицируются так же просто (с равным количеством шагов)

Alternate paths

Синий путь будет показывать одинаковое количество шагов и поворотов, в то время как красный путь имеет такое же количество шагов и меньшее количество оборотов. В моем коде у меня есть функция simplifyPath(), которая удаляет шаги, где изменяется направление, поэтому, если бы я мог получить все возможные пути от astar, тогда я мог бы выбрать тот, у которого наименьший оборот, но это не так, как A* в принципе работает, поэтому я ищу способ включить простоту в эвристику.

Вот мой текущий код:

var img,
    field = document.getElementById('field'),
    EngineBuilder = function(field, size) {
        var context = field.getContext("2d"),
            graphSettings = { size: size, mid: Math.ceil(size/2)},
            engine = {
                getPosition: function(event) {
                    var bounds = field.getBoundingClientRect(),
                        x = Math.floor(((event.clientX - bounds.left)/field.clientWidth)*field.width),
                        y = Math.floor(((event.clientY - bounds.top)/field.clientHeight)*field.height),
                        node = graph.grid[Math.floor(y/graphSettings.size)][Math.floor(x/graphSettings.size)];

                    return {
                        x: x,
                        y: y,
                        node: node
                    }
                },
                drawObstructions: function() {
                    context.clearRect (0, 0, 320, 200);
                    if(img) {
                        context.drawImage(img, 0, 0);
                    } else {
                        context.fillStyle = 'rgb(0, 0, 0)';
                        context.fillRect(200, 100, 50, 50);
                        context.fillRect(0, 100, 50, 50);
                        context.fillRect(100, 100, 50, 50);
                        context.fillRect(0, 50, 150, 50);
                    }
                },
                simplifyPath: function(start, complexPath, end) {
                    var previous = complexPath[1], simplePath = [start, {x:(previous.y*graphSettings.size)+graphSettings.mid, y:(previous.x*graphSettings.size)+graphSettings.mid}], i, classification, previousClassification;
                    for(i = 1; i < (complexPath.length - 1); i++) {
                        classification = (complexPath[i].x-previous.x).toString()+':'+(complexPath[i].y-previous.y).toString();
                        
                        if(classification !== previousClassification) {
                            simplePath.push({x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid});
                        } else {
                            simplePath[simplePath.length-1]={x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid};
                        }
                        previous = complexPath[i];
                        previousClassification = classification;
                    }
                    simplePath.push(end);
                    return simplePath;
                },
                drawPath: function(start, end) {
                    var path, step, next;
                    if(this.isPathClear(start, end)) {
                       this.drawLine(start, end);
                    } else {
                        path = this.simplifyPath(start, astar.search(graph, start.node, end.node), end);
                        if(path.length > 1) {
                            step = path[0];
                            for(next = 1; next < path.length; next++) {
                                this.drawLine(step, path[next]);
                                step = path[next];
                            }
                        }
                    }
                },
                drawLine: function(start, end) {
                    var x = start.x,
                        y = start.y,
                        dx = Math.abs(end.x - start.x),
                        sx = start.x<end.x ? 1 : -1,
                        dy = -1 * Math.abs(end.y - start.y),
                        sy = start.y<end.y ? 1 : -1,
                        err = dx+dy,
                        e2, pixel;

                    for(;;) {
                        pixel = context.getImageData(x, y, 1, 1).data[3];
                        if(pixel === 255) {
                            context.fillStyle = 'rgb(255, 0, 0)';
                        } else {
                            context.fillStyle = 'rgb(0, 255, 0)';
                        }
                        context.fillRect(x, y, 1, 1);
                        
                        if(x === end.x && y === end.y) {
                            break;
                        } else {
                            e2 = 2 * err;
                            if(e2 >= dy) {
                                err += dy;
                                x += sx;
                            }
                            if(e2 <= dx) {
                                err += dx;
                                y += sy;
                            }
                        }
                    }
                },
                isPathClear: function(start, end) {
                    var x = start.x,
                        y = start.y,
                        dx = Math.abs(end.x - start.x),
                        sx = start.x<end.x ? 1 : -1,
                        dy = -1 * Math.abs(end.y - start.y),
                        sy = start.y<end.y ? 1 : -1,
                        err = dx+dy,
                        e2, pixel;
                    
                    for(;;) {
                        pixel = context.getImageData(x, y, 1, 1).data[3];
                        if(pixel === 255) {
                            return false;
                        }
                        
                        if(x === end.x && y === end.y) {
                            return true;
                        } else {
                            e2 = 2 * err;
                            if(e2 >= dy) {
                                err += dy;
                                x += sx;
                            }
                            if(e2 <= dx) {
                                err += dx;
                                y += sy;
                            }
                        }
                    }
                }
            }, graph;
            engine.drawObstructions();
            graph = (function() {
                var x, y, rows = [], cols, js = '[';
                for(y = 0; y < 200; y += graphSettings.size) {
                    cols = [];
                    
                    for(x = 0; x < 320; x += graphSettings.size) {
                        cols.push(context.getImageData(x+graphSettings.mid, y+graphSettings.mid, 1, 1).data[3] === 255 ? 0 : 1);
                    }
                    js += '['+cols+'],\n';
                    rows.push(cols);
                }
                js = js.substring(0, js.length - 2);
                js += ']';
                document.getElementById('Graph').value=js;
                return new Graph(rows, { diagonal: true });
            })();
            return engine;
        }, start, end, engine = EngineBuilder(field, 10);

field.addEventListener('click', function(event) {
    var position = engine.getPosition(event);
    if(!start) {
        start = position;
    } else {
        end = position;
    }
    if(start && end) {
        engine.drawObstructions();
        engine.drawPath(start, end);
        start = end;
    }
}, false);
#field {
border: thin black solid;
    width: 98%;
    background: #FFFFC7;
}
#Graph {
    width: 98%;
    height: 300px;
    overflow-y: scroll;
}
<script src="http://jason.sperske.com/adventure/astar.js"></script>
<code>Click on any 2 points on white spaces and a path will be drawn</code>
<canvas id='field' height
    
='200' width='320'></canvas>
<textarea id='Graph' wrap='off'></textarea>
4b9b3361

Ответ 1

Очень интересная проблема! Спасибо за этот вопрос! Итак... некоторые наблюдения сначала:

Невозможность диагонального движения устраняет эту проблему, но поскольку вы заинтересованы в диагональном движении, мне пришлось искать больше.

Я посмотрел на пути, упрощающие алгоритмы вроде: Рамер Дуглас Пеукер (http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) и реализация: https://gist.github.com/rhyolight/2846020. Я добавил реализацию кода без успеха. Этот алгоритм не учитывает препятствия, поэтому его трудно адаптировать.

Интересно, каково было бы поведение (для диагональных движений), если бы вы использовали Dijkstra вместо A *, или если вы использовали "все кратчайшие пути между двумя парами узлов", а затем отсортировали их, увеличив изменения направления.

Прочитав немного о A * здесь http://buildnewgames.com/astar/ Я думал, что реализация A-звезды, которую вы используете, является проблемой или эвристики. Я пробовал все эвристики на а-звезде вашего кода, включая евклидова, который я закодировал сам, и попробовал также все эвристики в http://buildnewgames.com/astar К сожалению, все диагонали, позволяющие эвристику, имеют одинаковую проблему, которую вы описываете.

Я начал работать с их кодом, потому что это сетка "один-к-одному", и ваш вопрос дал мне вопрос рисования. Ваш simplifyPath, который я пытался удалить, также вызывал дополнительные проблемы. Вы должны иметь в виду, что поскольку вы делаете переназначение, это может быть проблема, основанная на этом

  • На квадратной сетке, которая позволяет 4 направления движения, используйте расстояние Манхэттена (L1).
  • На квадратной сетке, которая позволяет 8 направлений движения, используйте диагональное расстояние (L∞).
  • На квадратной сетке, которая позволяет любое направление движения, вы можете или не захотите евклидово расстояние (L2). Если A * находит пути на сетке, но вы разрешаете движение не по сетке, вы можете рассмотреть другие представления карты. (из http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html)

Что такое мой псевдокодовый алгоритм:

var path = A-star();
for each node in path {
    check all following nodes till some lookahead limit
    if you find two nodes in the same row but not column or in the same column but not row { 
       var nodesToBeStraightened = push all nodes to be "straightened" 
       break the loop;
    }
    skip loop index to the next node after zig-zag
}

if nodesToBeStraightened length is at least 3 AND
   nodesToBeStraightened nodes don't form a line AND
   the resulting Straight line after simplification doesn't hit an obstruction

       var straightenedPath =  straighten by getting the first and last elements of nodesToBeStraightened  and using their coordinates accordingly

return straightenedPath;

Вот визуальное объяснение того, что сравнивается в алгоритме:

Визуальное пояснение: Visual Representation of the algorithm

Как этот код будет использоваться с вами (я сделал большинство изменений - я старался изо всех сил, но есть так много проблем, как с тем, как вы рисуете, и из-за округления сетки и т.д. - вам нужно использовать сетку и сохранить масштаб траекторий точным - см. также предположения ниже):

    var img,
    field = document.getElementById('field'),
    EngineBuilder = function(field, size) {
        var context = field.getContext("2d"),
        graphSettings = { size: size, mid: Math.ceil(size/2)},
        engine = {
            //[...] missing code
            removeZigZag: function(currentPath,lookahead){
                //for each of the squares on the path - see lookahead more squares and check if it is in the path
                for (var i=0; i<currentPath.length; i++){

                    var toBeStraightened = [];
                    for (var j=i; j<lookahead+i+1 && j<currentPath.length; j++){         
                        var startIndexToStraighten = i;
                        var endIndexToStraighten = i+1;

                        //check if the one from lookahead has the same x xor the same y with one later node in the path
                        //and they are not on the same line
                        if( 
                           (currentPath[i].x == currentPath[j].x && currentPath[i].y != currentPath[j].y) ||
                           (currentPath[i].x == currentPath[j].y && currentPath[i].x != currentPath[j].x)   
                        ) { 
                            endIndexToStraighten = j;

                            //now that we found something between i and j push it to be straightened
                            for (var k = startIndexToStraighten; k<=endIndexToStraighten; k++){
                                toBeStraightened.push(currentPath[k]);
                            }
                            //skip the loop forward
                            i = endIndexToStraighten-1;
                            break;
                        }
                    }

                    if (toBeStraightened.length>=3                                   
                        && !this.formsALine(toBeStraightened) 
                        && !this.lineWillGoThroughObstructions(currentPath[startIndexToStraighten], currentPath[endIndexToStraighten],this.graph?????)
                        ){
                        //straightening:
                        this.straightenLine(currentPath, startIndexToStraighten, endIndexToStraighten);
                    }
                }
                return currentPath;
            },

            straightenLine: function(currentPath,fromIndex,toIndex){
                for (var l=fromIndex; l<=toIndex; l++){

                    if (currentPath[fromIndex].x == currentPath[toIndex].x){
                         currentPath[l].x = currentPath[fromIndex].x;
                    }
                    else if (currentPath[fromIndex].y == currentPath[toIndex].y){
                         currentPath[l].y = currentPath[fromIndex].y;       
                    }
                }
            },

            lineWillGoThroughObstructions: function(point1, point2, graph){
                var minX = Math.min(point1.x,point2.x);
                var maxX = Math.max(point1.x,point2.x);
                var minY = Math.min(point1.y,point2.y);
                var maxY = Math.max(point1.y,point2.y);

                //same row
                if (point1.y == point2.y){
                    for (var i=minX; i<=maxX && i<graph.length; i++){
                        if (graph[i][point1.y] == 1){ //obstacle
                            return true;
                        } 
                    }
                }
                //same column
                if (point1.x == point2.x){
                    for (var i=minY; i<=maxY && i<graph[0].length; i++){
                        if (graph[point1.x][i] == 1){ //obstacle
                            return true;
                        } 
                    }
                }
                return false;
            },

            formsALine: function(pointsArray){
                //only horizontal or vertical
                if (!pointsArray || (pointsArray && pointsArray.length<1)){
                    return false;
                }

                var firstY = pointsArray[0].y;
                var lastY = pointsArray[pointsArray.length-1].y;

                var firstX = pointsArray[0].x;
                var lastX = pointsArray[pointsArray.length-1].x;

                //vertical line
                if (firstY == lastY){
                    for (var i=0; i<pointsArray.length; i++){
                        if (pointsArray[i].y!=firstY){
                            return false;
                        }
                    }
                    return true;
                }

                //horizontal line
                else if (firstX == lastX){
                    for (var i=0; i<pointsArray.length; i++){
                        if (pointsArray[i].x!=firstX){
                            return false;
                        }
                    }
                    return true;
                }       
                return false;
            }
            //[...] missing code
        }
        //[...] missing code
    }

Предположения и Несовместимость вышеприведенного кода:

  • препятствие равно 1, а не 0
  • оригинальный код, который у меня есть в демо, использует массив вместо {x: number, y: number}
  • если вы используете другую реализацию a-star, точка 1 находится в столбце 1 и строке 2.
  • преобразуется в ваш {x: число, y: число}, но не проверял все части:
  • Я не мог получить доступ к объекту графа, чтобы получить препятствия - см.?????
  • Вы должны вызвать мой removeZigZag с помощью lookghead, например, 7 (шаги) в том месте, где вы были упрощение вашего пути.
  • По правде говоря, их код не так хорош по сравнению с реализацией a-star из Стэнфорда, но для наших целей он должен быть релевантным
  • Возможно, в коде есть ошибки, о которых я не знаю и которые могут быть улучшены. Также я выполнил свои проверки только в этой конкретной конфигурации мира.
  • Я считаю, что код имеет сложность O (N x lookahead) ~ O (N), где N - количество шагов на кратчайшем пути ввода A *.

Вот код в моем репозитории github (вы можете запустить демо) https://github.com/zifnab87/AstarWithDiagonalsFixedZigZag

Их clickHandling и код границы мира нарушаются, так как при щелчке по правой стороне карты вычисление пути не работает. У меня не было времени найти их ошибку. В результате у моего кода такая же проблема Вероятно, это потому, что карта, которую я поставил из вашего вопроса, не квадратная, но в любом случае мой алгоритм не должен быть затронут. Вы увидите, что это странное поведение происходит, если не выполняется мой код ZigZag. ( Изменить: это было фактически потому, что карта не была квадратной - я обновил карту, чтобы быть квадратной на данный момент)

Не стесняйтесь играть, раскомментируя эту строку, чтобы увидеть ее до следующего:

result = removeZigZag(result,7);

Я добавил 3 перед установками изображений, чтобы результаты можно было визуализировать: (Имейте в виду, чтобы начать матч и цель, если вы попробуете их - направление имеет значение;))

Случай 1: До Case 1: Before Случай 1: после Case 1: After Случай 2: До Case 2 Before Случай 2: После Case 2: After Случай 3: До Case 3: Before Случай 3: После Case 3: After Случай 4: До Case 4: Before Случай 4: После Case 4: After Ресурсы:

Ответ 2

Вы можете использовать модифицированный алгоритм A * для учета изменений в направлении. Хотя упрощение результата стандартного алгоритма A * может дать хорошие результаты, оно может оказаться не оптимальным. Этот модифицированный алгоритм A * вернет путь минимальной длины с наименьшим числом поворотов.

В модифицированном алгоритме A * каждая позиция соответствует восьми различным узлам, каждый из которых имеет свой собственный заголовок. Например, позиция (1, 1) соответствует восьми узлам

(1,1)-up, (1,1)-down, (1,1)-right, (1,1)-left,

(1,1)-up-left, (1,1)-up-right, (1,1)-down-left и (1,1)-down-right

Эвристическое расстояние от node до цели - эвристическое расстояние от соответствующей точки до цели. В этом случае вы, вероятно, захотите использовать следующую функцию:

H(point) = max(abs(goal.xcor-point.xcor), abs(goal.ycor-point.ycor))

Узлы, соответствующие определенной позиции, соединяются с узлами соседних позиций соответствующим заголовком. Например, узлы, соответствующие позиции (1,1), все подключены к следующим восьми узлам

(1,2)-up, (1,0)-down, (2,1)-right, (0,1)-left,

(0,2)-up-left, (2,2)-up-right, (0,0)-down-left и (2,0)-down-right

Расстояние между любыми двумя связанными узлами зависит от их заголовка. Если они имеют одну и ту же голову, тогда расстояние 1, в противном случае мы сделали поворот, поэтому расстояние 1+epsilon. epsilon представляет собой сколь угодно малое значение/число.

Мы знаем, что нужно иметь специальный случай как для начала, так и для цели. Начало и цель представлены в виде единственного node. В начале у нас нет заголовка, поэтому расстояние между началом node и любым подключенным node равно 1.

Теперь мы можем запустить стандартный алгоритм A * на модифицированном графике. Мы можем сопоставить возвращаемый путь к пути в исходной сетке, игнорируя заголовки. Общая длина возвращаемого пути будет иметь вид n+m*epsilon. n - общая длина соответствующего пути в исходной сетке, а m - количество витков. Поскольку A * возвращает путь минимальной длины, путь в исходной сетке - это путь минимальной длины, который делает наименьшие повороты.

Ответ 3

Я придумал несколько исправлений, которые являются простым дополнением к исходному коду, но он не работает во всех ситуациях (см. рисунок ниже), потому что мы ограничены тем, что возвращает A *. Вы можете увидеть my jsfiddle здесь

Я добавил следующий код к вашей функции simplifyPath прямо перед возвратом. То, что он делает, это вырезать лишние шаги, видя, есть ли четкий путь между несмежными шагами (сначала просматривая большие пробелы). Он может быть оптимизирован, но вы должны получить суть из того, что у меня есть.

do{
    shortened = false;
    loop:
    for(i = 0; i < simplePath.length; i++) {
        for(j = (simplePath.length - 1); j > (i + 1); j--) {
            if(this.isPathClear(simplePath[i],simplePath[j])) {
                simplePath.splice((i + 1),(j - i - 1));
                shortened = true;
                break loop;
            }
        }
    }
} while(shortened == true);

Ниже вы можете видеть, что это удаляет путь, который идет слева (как в вопросе), но также и то, что не все нечетные повороты удаляются. Это решение использует только точки, предоставленные от A *, а не точки между ними на пути, например, потому что у 2-й точки нет прямой беспрепятственной линии до 4-го или 5-го пунктов, она не может оптимизировать точку 3. Это бывает намного меньше, чем исходный код, но он по-прежнему дает странные результаты. incorrect path

Ответ 4

В случае риска потенциального голосования я попытаюсь изо всех сил предложить вам ответ. Если вы не используете сторонний плагин, я бы предложил создать простой объект pop/push stack, так как вы используете плагин какого-то другого, лучше всего попытаться работать вместе, а не против него.

Как говорится, я могу просто сделать что-то простое, например, отслеживать результаты моего вывода и попытаться логически определить правильный ответ. Я бы сделал простой экземпляр объекта типа объекта для хранения в массиве всех возможных путей? Таким образом, весь жизненный цикл объекта предназначен только для хранения информации о местоположении. Затем вы можете проанализировать этот массив объектов, ищущих наименьшее количество оборотов.

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

Мне кажется, что этот ответ слишком прост, поэтому он должен быть неправильным, но я постараюсь, тем не менее...

//Entity Type Object Literal
var pathsFound = function() {

    //Path Stats
    straightLine: false,
    turnCount: 0,
    xPos: -1, //Probably should not be instantiated -1 but for now it fine
    yPos: -1,

    //Getters
    isStraightLine: function() { return this.straightLine; },
    getTurnCount: function() { return this.turnCount; },
    getXPos: function() { return this.xPos; },
    getYPos: function() { return this.yPos; },

    //Setters
    setStraightLine: function() { this.straightLine = true; },
    setCrookedLine: function() { this.straightLine = false; },
    setXPos: function(val) { this.xPos = val; },
    setYPos: function(val) { this.yPos = val; },

    //Class Functionality
    incrementTurnCounter: function() { this.turnCount++; },
    updateFullPosition: function(xVal, yVal) { 
        this.xPos = xVal;
        this.yPos = yVal. 
    },
}

Таким образом, вы можете сообщать обо всех данных на каждом шагу и перед тем, как рисовать на экране, вы могли бы перебирать массив этих объектных литералов и находить правильный путь по наименьшему turnCount.

Ответ 5

var img,
    field = document.getElementById('field'),
    EngineBuilder = function(field, size) {
        var context = field.getContext("2d"),
            graphSettings = { size: size, mid: Math.ceil(size/2)},
            engine = {
                getPosition: function(event) {
                    var bounds = field.getBoundingClientRect(),
                        x = Math.floor(((event.clientX - bounds.left)/field.clientWidth)*field.width),
                        y = Math.floor(((event.clientY - bounds.top)/field.clientHeight)*field.height),
                        node = graph.grid[Math.floor(y/graphSettings.size)][Math.floor(x/graphSettings.size)];

                    return {
                        x: x,
                        y: y,
                        node: node
                    }
                },
                drawObstructions: function() {
                    context.clearRect (0, 0, 320, 200);
                    if(img) {
                        context.drawImage(img, 0, 0);
                    } else {
                        context.fillStyle = 'rgb(0, 0, 0)';
                        context.fillRect(200, 100, 50, 50);
                        context.fillRect(0, 100, 50, 50);
                        context.fillRect(100, 100, 50, 50);
                        context.fillRect(0, 50, 150, 50);
                    }
                },
                simplifyPath: function(start, complexPath, end) {
                    var previous = complexPath[1], simplePath = [start, {x:(previous.y*graphSettings.size)+graphSettings.mid, y:(previous.x*graphSettings.size)+graphSettings.mid}], i, classification, previousClassification;
                    for(i = 1; i < (complexPath.length - 1); i++) {
                        classification = (complexPath[i].x-previous.x).toString()+':'+(complexPath[i].y-previous.y).toString();
                        
                        if(classification !== previousClassification) {
                            simplePath.push({x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid});
                        } else {
                            simplePath[simplePath.length-1]={x:(complexPath[i].y*graphSettings.size)+graphSettings.mid, y:(complexPath[i].x*graphSettings.size)+graphSettings.mid};
                        }
                        previous = complexPath[i];
                        previousClassification = classification;
                    }
                    simplePath.push(end);
                    return simplePath;
                },
                drawPath: function(start, end) {
                    var path, step, next;
                    if(this.isPathClear(start, end)) {
                       this.drawLine(start, end);
                    } else {
                        path = this.simplifyPath(start, astar.search(graph, start.node, end.node), end);
                        if(path.length > 1) {
                            step = path[0];
                            for(next = 1; next < path.length; next++) {
                                this.drawLine(step, path[next]);
                                step = path[next];
                            }
                        }
                    }
                },
                drawLine: function(start, end) {
                    var x = start.x,
                        y = start.y,
                        dx = Math.abs(end.x - start.x),
                        sx = start.x<end.x ? 1 : -1,
                        dy = -1 * Math.abs(end.y - start.y),
                        sy = start.y<end.y ? 1 : -1,
                        err = dx+dy,
                        e2, pixel;

                    for(;;) {
                        pixel = context.getImageData(x, y, 1, 1).data[3];
                        if(pixel === 255) {
                            context.fillStyle = 'rgb(255, 0, 0)';
                        } else {
                            context.fillStyle = 'rgb(0, 255, 0)';
                        }
                        context.fillRect(x, y, 1, 1);
                        
                        if(x === end.x && y === end.y) {
                            break;
                        } else {
                            e2 = 2 * err;
                            if(e2 >= dy) {
                                err += dy;
                                x += sx;
                            }
                            if(e2 <= dx) {
                                err += dx;
                                y += sy;
                            }
                        }
                    }
                },
                isPathClear: function(start, end) {
                    var x = start.x,
                        y = start.y,
                        dx = Math.abs(end.x - start.x),
                        sx = start.x<end.x ? 1 : -1,
                        dy = -1 * Math.abs(end.y - start.y),
                        sy = start.y<end.y ? 1 : -1,
                        err = dx+dy,
                        e2, pixel;
                    
                    for(;;) {
                        pixel = context.getImageData(x, y, 1, 1).data[3];
                        if(pixel === 255) {
                            return false;
                        }
                        
                        if(x === end.x && y === end.y) {
                            return true;
                        } else {
                            e2 = 2 * err;
                            if(e2 >= dy) {
                                err += dy;
                                x += sx;
                            }
                            if(e2 <= dx) {
                                err += dx;
                                y += sy;
                            }
                        }
                    }
                }
            }, graph;
            engine.drawObstructions();
            graph = (function() {
                var x, y, rows = [], cols, js = '[';
                for(y = 0; y < 200; y += graphSettings.size) {
                    cols = [];
                    
                    for(x = 0; x < 320; x += graphSettings.size) {
                        cols.push(context.getImageData(x+graphSettings.mid, y+graphSettings.mid, 1, 1).data[3] === 255 ? 0 : 1);
                    }
                    js += '['+cols+'],\n';
                    rows.push(cols);
                }
                js = js.substring(0, js.length - 2);
                js += ']';
                document.getElementById('Graph').value=js;
                return new Graph(rows, { diagonal: true });
            })();
            return engine;
        }, start, end, engine = EngineBuilder(field, 10);

field.addEventListener('click', function(event) {
    var position = engine.getPosition(event);
    if(!start) {
        start = position;
    } else {
        end = position;
    }
    if(start && end) {
        engine.drawObstructions();
        engine.drawPath(start, end);
        start = end;
    }
}, false);
#field {
border: thin black solid;
    width: 98%;
    background: #FFFFC7;
}
#Graph {
    width: 98%;
    height: 300px;
    overflow-y: scroll;
}
<script src="http://jason.sperske.com/adventure/astar.js"></script>
<code>Click on any 2 points on white spaces and a path will be drawn</code>
<canvas id='field' height
    
='200' width='320'></canvas>
<textarea id='Graph' wrap='off'></textarea>