TyrealGray/Qin.js

View on GitHub
packages/qin.js/src/core/reactorCore/gameCore/navigation/terrainNavigator.js

Summary

Maintainability
A
3 hrs
Test Coverage
//@flow

import Grid from './grid';
import PathNode from './pathNode';
import Navigator from './navigator';

type TerrainNavigatorPropsType = {
    terrain: Object,
};

/**
 * class of a terrain navigator which provide navigation router
 * @extends Navigator
 */
class TerrainNavigator extends Navigator {
    /**
     * create a terrain navigator
     * @param props {Object}
     */
    constructor(props: TerrainNavigatorPropsType) {
        super(props);
    }

    /**
     * get navigation from here(current) to here(destination)
     * @param destinationInfo {Object}
     * @param destinationInfo.current {TerrainGrid} start point of the grid
     * @param destinationInfo.destination {TerrainGrid} destination point of the grid
     * @returns {Array<Grid>} array of Grid
     * @abstract
     */
    getNavigation(destinationInfo: {
        current: Grid,
        destination: Grid,
    }): Array<Grid> {
        const { current, destination } = destinationInfo;
        const startNode: PathNode = new PathNode({
            point: current,
            step: 0,
            destination: destination,
            from: null,
        });

        if (startNode.isAtDestination()) {
            return [];
        }

        return this.getCorrectPath(startNode);
    }

    /**
     * get an array of grid that should navigate a correct path, return empty array if there is no correct path
     * @param startNode {PathNode} array of PathNode
     * @returns {Array<Grid>} array of Grid
     */
    getCorrectPath(startNode: PathNode): Array<Grid> {
        const pathNodes = startNode.getNeighbors();
        const scannedPaths: Array<PathNode> = [startNode];
        const correctPath = [];

        let openPaths: Array<PathNode> = pathNodes;
        let correctNode: PathNode | null = null;
        let shouldScan: boolean = !(correctNode || !openPaths.length);

        while (shouldScan) {
            const {
                node,
                closedPaths,
                newOpenPaths,
            } = TerrainNavigator.getCorrectNode(openPaths);

            correctNode = node;

            scannedPaths.push(...closedPaths);

            openPaths = this.excludeScannedPath(openPaths, scannedPaths);

            openPaths.push(...newOpenPaths);

            shouldScan = !( correctNode || !openPaths.length || openPaths.length > 50);
        }

        if (correctNode) {
            let lastNode: PathNode | null = correctNode.getFrom();
            correctPath.unshift(correctNode.getLocation());

            while (lastNode && lastNode.getFrom()) {
                correctPath.unshift(lastNode.getLocation());
                lastNode = lastNode.getFrom();
            }
        }

        return correctPath;
    }

    excludeScannedPath(
        openPaths: Array<PathNode>,
        scannedPaths: Array<PathNode>,
    ): Array<PathNode> {
        const result: Array<PathNode> = [];

        for (const openPath of openPaths) {
            const shouldBeExcluded = scannedPaths.find((path) => {
                return openPath.getLocation() === path.getLocation();
            });

            if (shouldBeExcluded) {
                continue;
            }

            result.push(openPath);
        }

        return result;
    }

    static getCorrectNode(
        paths: Array<PathNode>,
    ): {
        node: PathNode | null,
        closedPaths: Array<PathNode>,
        newOpenPaths: Array<PathNode>,
    } {
        let node = null;

        const pathsMatchWeight: Array<PathNode> = [];
        const smallestWeight = TerrainNavigator.getSmallestWeight(paths);
        const priorityPathsByStep: Array<PathNode> = [];

        const closedPaths = [];
        const newOpenPaths = [];

        for (const path of paths) {
            if (path.getWeight() === smallestWeight) {
                pathsMatchWeight.push(path);
            }
        }

        pathsMatchWeight.sort((a, b) => a.getStep() - b.getStep());

        const priorityStepNumber = pathsMatchWeight[0].getStep();

        for (const path of pathsMatchWeight) {
            if (path.getStep() !== priorityStepNumber) {
                break;
            }

            priorityPathsByStep.push(path);
        }

        for (const path of priorityPathsByStep) {
            if (path.isAtDestination()) {
                node = path;
                break;
            }

            closedPaths.push(path);
            newOpenPaths.push(...path.getNeighbors());
        }

        return { node, closedPaths, newOpenPaths };
    }

    static getSmallestWeight(paths: Array<PathNode>): number {
        const weights = [];
        for (const path of paths) {
            weights.push(path.getWeight());
        }

        weights.sort((a, b) => a - b);

        return weights[0];
    }
}

export default TerrainNavigator;