Mirroar/hivemind

View on GitHub
src/creep/traffic-manager.ts

Summary

Maintainability
C
1 day
Test Coverage
import {getCostMatrix, getDangerMatrix} from 'utils/cost-matrix';
import {handleMapArea} from 'utils/map';

declare global {
    interface Creep {
        _blockingCreepMovement?: Creep | PowerCreep;
        _hasMoveIntent?: boolean;
        _requestedMoveArea?: {
            pos: RoomPosition;
            range: number;
        };
        _alternatePositions?: RoomPosition[];
    }

    interface PowerCreep {
        _blockingCreepMovement?: Creep | PowerCreep;
        _hasMoveIntent?: boolean;
        _requestedMoveArea?: {
            pos: RoomPosition;
            range: number;
        };
        _alternatePositions?: RoomPosition[];
    }
}

export default class TrafficManager {
    dangerMatrix: CostMatrix;

    setPreferredArea(creep: Creep | PowerCreep, center: RoomPosition, range: number) {
        creep._requestedMoveArea = {
            pos: center,
            range,
        };
    }

    setAlternatePositions(creep: Creep | PowerCreep, positions: RoomPosition[]) {
        creep._alternatePositions = positions;
    }

    hasAlternatePosition(creep: Creep | PowerCreep) {
        return Boolean(creep._requestedMoveArea) || Boolean(creep._alternatePositions);
    }

    setMoving(creep: Creep | PowerCreep) {
        creep._hasMoveIntent = true;
    }

    /**
     * Notify a creep that it's blocking another one.
     *
     * @param {Creep | PowerCreep} creep The creep being blocked.
     * @param {Creep | PowerCreep} blockingCreep The creep blocking the path.
     */
    setBlockingCreep(creep: Creep | PowerCreep, blockingCreep: Creep | PowerCreep) {
        blockingCreep._blockingCreepMovement = creep;
    }

    manageTraffic() {
        const powerCreeps = _.filter(Game.powerCreeps, creep => (creep.ticksToLive || 0) > 0 && creep.shard === Game.shard.name);

        // Move blocking creeps if necessary.
        _.each([..._.values<Creep>(Game.creeps), ...powerCreeps], creep => {
            if (!creep._blockingCreepMovement) return;
            if (creep._hasMoveIntent) return;

            const blockedCreep = creep._blockingCreepMovement;
            if (blockedCreep instanceof Creep && blockedCreep.fatigue) return;

            this.dangerMatrix = getDangerMatrix(creep.room.name);

            if (creep.pos.getRangeTo(blockedCreep) === 1) {
                const alternatePosition = this.getAlternateCreepPosition(creep);
                if (alternatePosition) {
                    // Move aside for the other creep.
                    creep.move(creep.pos.getDirectionTo(alternatePosition));
                    blockedCreep.move(blockedCreep.pos.getDirectionTo(creep.pos));
                }
                else {
                    // Swap with blocked creep.
                    creep.move(creep.pos.getDirectionTo(blockedCreep.pos));
                    blockedCreep.move(blockedCreep.pos.getDirectionTo(creep.pos));
                }
            }
            else {
                blockedCreep.moveTo(creep.pos, {range: 1});
            }

            creep._hasMoveIntent = true;
        });
    }

    getAlternateCreepPosition(creep: Creep | PowerCreep): RoomPosition | null {
        if (!creep._requestedMoveArea && !creep._alternatePositions) return null;

        let alternatePosition: RoomPosition;
        const costMatrix = getCostMatrix(creep.room.name, {
            singleRoom: Boolean(creep.memory.singleRoom),
        });

        // @todo Prefer moving onto roads / plains instead of swamps.
        const blockingCreeps: Array<Creep | PowerCreep> = [];
        handleMapArea(creep.pos.x, creep.pos.y, (x, y) => {
            if (costMatrix.get(x, y) >= 100) return null;
            if (creep.room.getTerrain().get(x, y) === TERRAIN_MASK_WALL) return null;
            if (this.dangerMatrix.get(x, y) > 0) return null;

            const pos = new RoomPosition(x, y, creep.room.name);
            if (!this.isAvailableAlternatePosition(creep, pos)) return null;

            const blockingCreep = pos.lookFor(LOOK_CREEPS);
            if (blockingCreep.length > 0) {
                blockingCreeps.push(blockingCreep[0]);
                return null;
            }

            const blockingPowerCreep = pos.lookFor(LOOK_POWER_CREEPS);
            if (blockingPowerCreep.length > 0) {
                blockingCreeps.push(blockingPowerCreep[0]);
                return null;
            }

            alternatePosition = pos;
            return false;
        });

        if (!alternatePosition && blockingCreeps.length > 0) {
            // If none of the alternate positions are free, check if
            // neighboring creeps can be pushed aside recursively.
            // @todo This should use BFS instead of DFS so we minimize the
            // number of move operations we have to do.
            // @todo Limit the depth of the search.
            for (const blockingCreep of blockingCreeps) {
                if (!blockingCreep.my) continue;
                if (blockingCreep._hasMoveIntent) continue;
                if (blockingCreep._blockingCreepMovement) continue;
                if (blockingCreep instanceof Creep && blockingCreep.fatigue) continue;

                blockingCreep._hasMoveIntent = true;
                const chainedAlternatePosition = this.getAlternateCreepPosition(blockingCreep);
                if (chainedAlternatePosition) {
                    // Move aside for the other creep.
                    blockingCreep.move(blockingCreep.pos.getDirectionTo(chainedAlternatePosition));
                    return blockingCreep.pos;
                }

                delete blockingCreep._hasMoveIntent;
            }
        }

        if (alternatePosition) {
            creep.room.visual.line(alternatePosition.x, alternatePosition.y, creep.pos.x, creep.pos.y, {
                color: '#00ff00',
            });
        }

        return alternatePosition;
    }

    isAvailableAlternatePosition(creep: Creep | PowerCreep, position: RoomPosition): boolean {
        if (creep._requestedMoveArea && position.getRangeTo(creep._requestedMoveArea.pos) <= creep._requestedMoveArea.range) return true;

        if (creep._alternatePositions) {
            for (const alternatePosition of creep._alternatePositions) {
                if (position.isEqualTo(alternatePosition)) return true;
            }
        }

        return false;
    }
}