Mirroar/hivemind

View on GitHub
src/role/scout.ts

Summary

Maintainability
C
1 day
Test Coverage
/* global RoomPosition OK */

import cache from 'utils/cache';
import container from 'utils/container';
import hivemind from 'hivemind';
import Role from 'role/role';
import {encodePosition, decodePosition} from 'utils/serialization';
import {getRoomIntel} from 'room-intel';

declare global {
    interface ScoutCreep extends Creep {
        memory: ScoutCreepMemory;
        heapMemory: ScoutCreepHeapMemory;
    }

    interface ScoutCreepMemory extends CreepMemory {
        role: 'scout';
        scoutTarget?: string;
        portalTarget?: string;
        invalidScoutTargets?: string[];
    }

    interface ScoutCreepHeapMemory extends CreepHeapMemory {
        moveWithoutNavMesh?: boolean;
        roomHistory: string[];
        posHistory: string[];
        lastPos: string;
        stuckCount: number;
        pauseUntil?: number;
    }
}

interface ScoutTarget extends RoomListEntry {
    roomName: string;
}

const accessibilityCache = {};

export default class ScoutRole extends Role {
    /**
     * Makes a creep behave like a scout.
     *
     * @param {Creep} creep
     *   The creep to run logic for.
     */
    run(creep: ScoutCreep) {
        if (creep.memory.disableNotifications) {
            // No attack notifications for scouts, please.
            creep.notifyWhenAttacked(false);
            delete creep.memory.disableNotifications;
        }

        if (!creep.memory.scoutTarget && !creep.memory.portalTarget) {
            this.chooseScoutTarget(creep);
        }

        this.performScout(creep);
    }

    /**
     * Makes this creep move between rooms to gather intel.
     *
     * @param {Creep} creep
     *   The creep to run logic for.
     */
    performScout(creep: ScoutCreep) {
        if (creep.memory.portalTarget) {
            const portalPosition = decodePosition(creep.memory.portalTarget);
            if (creep.pos.roomName === portalPosition.roomName) {
                creep.whenInRange(1, portalPosition, () => {
                    creep.moveTo(portalPosition);
                });
            }
            else {
                creep.moveToRoom(portalPosition.roomName);
            }

            return;
        }

        if (!creep.memory.scoutTarget) {
            // Just stand around somewhere.
            creep.whenInRange(3, new RoomPosition(25, 25, creep.pos.roomName), () => {});

            return;
        }

        if (typeof creep.room.visual !== 'undefined') {
            creep.room.visual.text(creep.memory.scoutTarget, creep.pos);
        }

        if (this.isOscillating(creep) || this.isStuck(creep)) this.chooseScoutTarget(creep, true);

        if (!creep.memory.scoutTarget) {
            // Just stand around somewhere.
            creep.whenInRange(3, new RoomPosition(25, 25, creep.pos.roomName), () => {});

            return;
        }

        const targetPosition = new RoomPosition(25, 25, creep.memory.scoutTarget);
        if (creep.interRoomTravel(targetPosition)) return;

        this.chooseScoutTarget(creep);
    }

    /**
     * Chooses which of the possible scout target rooms to travel to.
     *
     * @param {Creep} creep
     *   The creep to run logic for.
     * @param {boolean} invalidateOldTarget
     *   If true, the old scout target is deemed invalid and will no longer be
     *   scouted by this creep.
     */
    chooseScoutTarget(creep: ScoutCreep, invalidateOldTarget?: boolean) {
        if (creep.heapMemory.pauseUntil) {
            if (Game.time >= creep.heapMemory.pauseUntil) delete creep.heapMemory.pauseUntil;
            return;
        }

        if (creep.memory.scoutTarget && invalidateOldTarget) {
            if (!creep.memory.invalidScoutTargets) {
                creep.memory.invalidScoutTargets = [];
            }

            creep.memory.invalidScoutTargets.push(creep.memory.scoutTarget);
        }

        delete creep.memory.scoutTarget;
        delete creep.heapMemory.moveWithoutNavMesh;
        if (!creep.memory.origin) creep.memory.origin = creep.room.name;
        if (!Memory.strategy) return;
        if (!hivemind.segmentMemory.isReady()) return;

        const best = this.getBestScoutOption(creep);

        if (best) {
            creep.memory.scoutTarget = best.info.roomName;
            const roomIntel = getRoomIntel(best.info.roomName);
            roomIntel.registerScoutAttempt();
        }

        if (!creep.memory.scoutTarget) {
            creep.memory.scoutTarget = creep.memory.origin;
            creep.heapMemory.pauseUntil = Game.time + 50;
        }
    }

    getBestScoutOption(creep: ScoutCreep) {
        const startTime = Game.cpu.getUsed();
        const candidates = _.sortByAll(
            this.getScoutableRoomsForCreep(creep),
            (info: ScoutTarget) => -info.scoutPriority,
            (info: ScoutTarget) => {
                const roomIntel = getRoomIntel(info.roomName);
                return roomIntel.getLastScoutAttempt() + info.range * 50;
            },
        );

        for (const info of candidates) {
            if ((Game.cpu.getUsed() - startTime > 10)) {
                return null;
            }

            if (!this.hasRoomPath(creep, info.roomName)) {
                if (!creep.memory.invalidScoutTargets) {
                    creep.memory.invalidScoutTargets = [];
                }

                creep.memory.invalidScoutTargets.push(info.roomName);
                continue;
            }

            const roomIntel = getRoomIntel(info.roomName);
            const lastScout = roomIntel.getLastScoutAttempt();
            return {info, lastScout};
        }

        return null;
    }

    getScoutableRoomsForCreep(creep: ScoutCreep): ScoutTarget[] {
        return _.filter(this.getScoutableRoomsByOrigin(creep.memory.origin), (info: ScoutTarget) => {
            if (info.roomName === creep.pos.roomName) return false;
            if (creep.memory.invalidScoutTargets && creep.memory.invalidScoutTargets.includes(info.roomName)) return false;

            return true;
        });
    }

    getScoutableRoomsByOrigin(origin: string): ScoutTarget[] {
        return cache.inHeap('scoutableRooms:' + origin, 200, () => _.filter(this.getScoutableRooms(), (info: ScoutTarget) => {
            if (info.origin !== origin) return false;

            return true;
        }));
    }

    getScoutableRooms() {
        return cache.inHeap('scoutableRooms', 200, () => _.filter(
            _.map(
                Memory.strategy.roomList,
                (info: RoomListEntry, roomName: string) => ({...info, roomName}),
            ),
            (info: ScoutTarget) => {
                if (info.scoutPriority <= 0) return false;

                return true;
            },
        ));
    }

    hasRoomPath(creep: Creep, destination: string): boolean {
        return cache.inObject(accessibilityCache, creep.pos.roomName + '/' + destination, 5000, () => {
            const path = container.get('NavMesh').findPath(creep.pos, new RoomPosition(25, 25, destination));
            if (!path.incomplete) return true;

            return false;
        });
    }

    isOscillating(creep: ScoutCreep) {
        if (!creep.heapMemory.roomHistory) creep.heapMemory.roomHistory = [];
        const history = creep.heapMemory.roomHistory;

        if (history.length === 0 || history[history.length - 1] !== creep.pos.roomName) history.push(creep.pos.roomName);
        if (history.length > 20) creep.heapMemory.roomHistory = history.slice(-10);

        if (
            history.length >= 10
            && history[history.length - 1] === history[history.length - 3]
            && history[history.length - 2] === history[history.length - 4]
            && history[history.length - 3] === history[history.length - 5]
            && history[history.length - 4] === history[history.length - 6]
            && history[history.length - 5] === history[history.length - 7]
            && history[history.length - 6] === history[history.length - 8]
            && history[history.length - 7] === history[history.length - 9]
            && history[history.length - 8] === history[history.length - 10]
        ) {
            delete creep.heapMemory.roomHistory;
            return true;
        }

        return this.isTileOscillating(creep);
    }

    isTileOscillating(creep: ScoutCreep) {
        if (!creep.heapMemory.posHistory) creep.heapMemory.posHistory = [];
        const history = creep.heapMemory.posHistory;
        const pos = encodePosition(creep.pos);

        if (history.length === 0 || history[history.length - 1] !== pos) history.push(pos);
        if (history.length > 30) creep.heapMemory.posHistory = history.slice(-20);
        if (_.filter(history, v => v === pos).length >= 5) {
            delete creep.heapMemory.posHistory;
            return true;
        }

        return false;
    }

    isStuck(creep: ScoutCreep) {
        const pos = encodePosition(creep.pos);

        if (!creep.heapMemory.lastPos || creep.heapMemory.lastPos !== pos) {
            creep.heapMemory.lastPos = pos;
            creep.heapMemory.stuckCount = 1;
            return false;
        }

        if (creep.heapMemory.stuckCount++ < 10) return false;

        return true;
    }
}