Home Reference Source

src/helper/pathfinds/a-star.js

/**
 * @ignore
 */
export class AStar {
    constructor() {
        this.openList = [];
        this.closeList = [];

        this.NODE_DISTANCE_VALUE = 100;
    }

    findPath(nodes, startNode, endNode) {
        const path = [];
        let currentNode = null;
        let neighbours = [];
        let g = 0;
        let h = 0;
        let f = 0;

        this.addToOpenList(startNode);
        while (this.openList.length > 0) {
            currentNode = this.getCurrentNode();
            if (currentNode.posX === endNode.posX && currentNode.posY === endNode.posY) {
                break;
            }

            this.addToCloseList(currentNode);
            this.getNeighbours(currentNode, nodes).forEach(node => {
                if (this.isOnCloseList(node) || !node.walkable) {
                    return;
                }

                g = (!!node.parent ? node.parent.g : node.g) + this.NODE_DISTANCE_VALUE;
                h = (Math.abs(endNode.posX - node.posX) + Math.abs(endNode.posY - node.posY)) * this.NODE_DISTANCE_VALUE;
                f = h + g;

                if (this.isOnOpenList(node)) {
                    if (g < node.g) {
                        node.parent = currentNode;
                        node.g = g;
                        node.h = h;
                        node.f = f;
                    }
                } else {
                    this.addToOpenList(node);
                    node.parent = currentNode;
                    node.g = g;
                    node.h = h;
                    node.f = f;
                }
            });
        }

        if (this.openList.length === 0) {
            return path;
        }

        var lastNode = this.getNode(endNode.posX, endNode.posY, nodes);
        while (lastNode.posX !== startNode.posX || lastNode.posY !== startNode.posY) {
            path.push(lastNode);
            lastNode = lastNode.parent;
        }
        path.push(this.getNode(startNode.posX, startNode.posY, nodes));

        return path.reverse();
    }

    removeFromCloseList(node) {
        const index = this.closeList.findIndex(n => n.posX === node.posX && n.posY === node.posY);

        if (index > -1) {
            this.closeList.splice(index, 1);
        }
    }

    removeFromOpenList(node) {
        const index = this.openList.findIndex(n => n.posX === node.posX && n.posY === node.posY);

        if (index > -1) {
            this.openList.splice(index, 1);
        }
    }

    addToCloseList(node) {
        const hasNode = this.closeList.find(n => n.posX === node.posX && n.posY === node.posY);

        if (!hasNode) {
            this.removeFromOpenList(node);
            this.closeList.push(node);
        }
    }

    addToOpenList(node) {
        const hasNode = this.openList.find(n => n.posX === node.posX && n.posY === node.posY);

        if (!hasNode) {
            this.removeFromCloseList(node);
            this.openList.push(node);
        }
    }

    getCurrentNode() {
        let minF = 1000000;
        let currentNode = null;
        this.openList.forEach(node => {
            if (node.f < minF) {
                minF = node.f;
                currentNode = node;
            }
        });

        return currentNode;
    }

    getNeighbours(node, nodes) {
        const neighbours = [];

        if (this.getNode(node.posX, node.posY - 1, nodes)) {
            neighbours.push(this.getNode(node.posX, node.posY - 1, nodes));
        }

        if (this.getNode(node.posX, node.posY + 1, nodes)) {
            neighbours.push(this.getNode(node.posX, node.posY + 1, nodes));
        }

        if (this.getNode(node.posX - 1, node.posY, nodes)) {
            neighbours.push(this.getNode(node.posX - 1, node.posY, nodes));
        }

        if (this.getNode(node.posX + 1, node.posY, nodes)) {
            neighbours.push(this.getNode(node.posX + 1, node.posY, nodes));
        }

        return neighbours;
    }

    getNode(posX, posY, nodes) {
        return nodes.find(node => node.posX === posX && node.posY === posY);
    }

    isOnCloseList(node) {
        return this.closeList.some(n => n.posX === node.posX && n.posY === node.posY);
    }

    isOnOpenList(node) {
        return this.openList.some(n => n.posX === node.posX && n.posY === node.posY);
    }
}