From be66fe0822e48062787080b300aabdc515e876ab Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 15 Jul 2025 19:36:25 -0300 Subject: [PATCH] Better RoomRunner --- src/CreepRunner.ts | 36 +++++ src/RequisitionsManager.ts | 94 ++++++++++++ src/RoomInspector.ts | 59 ++++++++ src/RoomRunner.ts | 160 +------------------- src/utils/funcs/getRoomCreeps.ts | 8 + src/utils/funcs/sortCreepRolesByPriority.ts | 8 + tsconfig.json | 2 +- 7 files changed, 212 insertions(+), 155 deletions(-) create mode 100644 src/CreepRunner.ts create mode 100644 src/RequisitionsManager.ts create mode 100644 src/RoomInspector.ts create mode 100644 src/utils/funcs/getRoomCreeps.ts create mode 100644 src/utils/funcs/sortCreepRolesByPriority.ts diff --git a/src/CreepRunner.ts b/src/CreepRunner.ts new file mode 100644 index 0000000..72ebd44 --- /dev/null +++ b/src/CreepRunner.ts @@ -0,0 +1,36 @@ +import { CreepRole, CreepRoles } from "types/creeps"; +import { getRoomCreeps } from "utils/funcs/getRoomCreeps"; + +class CreepRunner { + public static run(room: Room, state: GameState): GameState { + for (const name in getRoomCreeps(room)) { + const creep = Game.creeps[name]; + + if (!creep) { + this.clearDeadCreepMemory(name, state); + continue; + } + + const roleDefinition = CreepRoles[creep.memory.role as CreepRole]; + if (!roleDefinition) { + this.clearDeadCreepMemory(name, state); + continue; + } + + state = roleDefinition.handler.run(creep, state); + } + + return state; + } + + private static clearDeadCreepMemory(creepName: string, state: GameState): void { + console.log(`Creep ${creepName} is dead, cleaning up memory.`); + + const roleDefinition = CreepRoles[Memory.creeps[creepName].role as CreepRole]; + roleDefinition.handler.destroy(Memory.creeps[creepName], state); + + delete Memory.creeps[creepName]; // Clean up memory for dead creeps + } +} + +export default CreepRunner; diff --git a/src/RequisitionsManager.ts b/src/RequisitionsManager.ts new file mode 100644 index 0000000..86eb71d --- /dev/null +++ b/src/RequisitionsManager.ts @@ -0,0 +1,94 @@ +import { CreepRequisition, CreepRole, CreepRoles } from "types/creeps"; +import { DEFAULT_GAME_CONFIG } from "types/gameConfig"; +import { get_role_cost } from "utils/funcs/getRoleCost"; +import { getRoomCreeps } from "utils/funcs/getRoomCreeps"; +import { sortCreepRolesByPriority } from "utils/funcs/sortCreepRolesByPriority"; + +class RequisitionsManager { + public static validateState(room: Room, state: GameState): GameState { + const creepRequisition = this.getRoomRequisition(room); + + if (Object.values(creepRequisition).every(count => count <= 0)) { + return state; + } + + const totalCreeps = Object.values(room.find(FIND_MY_CREEPS)).length; + if (totalCreeps >= state.maxHarvesters) { + return state; // No need to spawn more creeps + } + + for (const spawn of room.find(FIND_MY_SPAWNS)) { + const requestResult = this.fulfillSpawnRequisition(spawn, creepRequisition); + + if (requestResult) { + console.log(`Spawn ${spawn.name} has fulfilled a creep requisition.`); + } else { + console.log(`Spawn ${spawn.name} could not fulfill any creep requisition.`); + } + } + + return state; + } + + private static fulfillSpawnRequisition(spawn: StructureSpawn, creepRequisition: CreepRequisition): boolean { + if (spawn.spawning) { + return false; + } + + const rolesToSpawn = sortCreepRolesByPriority(creepRequisition); + + for (const role of rolesToSpawn) { + if (spawn.store[RESOURCE_ENERGY] < get_role_cost(role)) { + continue; + } + + const newName = `${role.name}_${Game.time}`; + const spawnResult = spawn.spawnCreep(role.body, newName, { + memory: { + role: role.name, + room: spawn.room.name, + spawnId: spawn.id, + working: false + } + }); + if (spawnResult === OK) { + console.log(`Spawn ${spawn.name} successfully spawned a new ${role.name}: ${newName}.`); + return true; // Exit after spawning one creep + } else { + console.error(`Spawn ${spawn.name} failed to spawn a new ${role.name}: ${spawnResult}`); + } + } + + return false; // No creeps were spawned + } + + private static getRoomRequisition(room: Room): CreepRequisition { + const creepCounts: Record = {}; + for (const creepMemory of Object.values(getRoomCreeps(room))) { + const role = creepMemory.role; + creepCounts[role] = (creepCounts[role] || 0) + 1; + } + + const requisition: CreepRequisition = { + harvester: 0, + upgrader: 0, + builder: 0 + }; + for (const role in DEFAULT_GAME_CONFIG.minCreepsPerRole) { + if (!(role in CreepRoles)) { + console.log(`Unknown creep role: ${role}`); + continue; + } + const roleType = role as CreepRole; + requisition[roleType] = DEFAULT_GAME_CONFIG.minCreepsPerRole[roleType] - (creepCounts[role] || 0); + + if (requisition[roleType] < 0) { + requisition[roleType] = 0; // Ensure we don't have negative requisitions + } + } + + return requisition; + } +} + +export default RequisitionsManager; diff --git a/src/RoomInspector.ts b/src/RoomInspector.ts new file mode 100644 index 0000000..b5690a4 --- /dev/null +++ b/src/RoomInspector.ts @@ -0,0 +1,59 @@ +import { checkPositionWalkable } from "utils/funcs/checkPosition"; +import { + createSourcePositionMatrix, + forEachMatrixSpot, + getPositionWithDelta, + PositionSpotStatus, + setSpotStatus +} from "utils/positions"; + +class RoomInspector { + public static inspectState(room: Room, state: GameState): GameState { + if (!this.stateWasInitialized(state)) { + state = this.initializeState(room, state); + } + return state; + } + + private static stateWasInitialized(state: GameState): boolean { + return !!state.sourcesStates; + } + + private static initializeState(room: Room, state: GameState): GameState { + state.sourcesStates = {}; + state.maxHarvesters = 0; + + for (const source of room.find(FIND_SOURCES)) { + this.configureSourceState(source, state); + } + + return state; + } + + private static configureSourceState(source: Source, state: GameState): void { + const sourceId = source.id.toString(); + + if (!state.sourcesStates[sourceId]) { + state.sourcesStates[sourceId] = { + id: sourceId, + pos: source.pos, + spots: createSourcePositionMatrix() + }; + } + + forEachMatrixSpot(state.sourcesStates[sourceId].spots, (delta, status) => { + if (status !== PositionSpotStatus.UNKNOWN) { + return; // Skip known spots + } + const pos = getPositionWithDelta(source.pos, delta); + if (checkPositionWalkable(pos)) { + setSpotStatus(state.sourcesStates[sourceId].spots, delta, PositionSpotStatus.EMPTY); + state.maxHarvesters += 1; + } else { + setSpotStatus(state.sourcesStates[sourceId].spots, delta, PositionSpotStatus.INVALID); + } + }); + } +} + +export default RoomInspector; diff --git a/src/RoomRunner.ts b/src/RoomRunner.ts index 4e722bc..b46e760 100644 --- a/src/RoomRunner.ts +++ b/src/RoomRunner.ts @@ -1,162 +1,14 @@ -import { CreepRequisition, CreepRole, CreepRoles, RoleDefinition } from "types/creeps"; -import { DEFAULT_GAME_CONFIG } from "types/gameConfig"; -import { checkPositionWalkable } from "utils/funcs/checkPosition"; -import { get_role_cost as get_role_cost } from "utils/funcs/getRoleCost"; -import { - createSourcePositionMatrix, - forEachMatrixSpot, - getPositionWithDelta, - PositionSpotStatus, - setSpotStatus -} from "utils/positions"; +import CreepRunner from "CreepRunner"; +import RequisitionsManager from "RequisitionsManager"; +import RoomInspector from "RoomInspector"; class RoomRunner { public static run(room: Room, state: GameState): GameState { - this.updateSpawnState(room, state); + state = RoomInspector.inspectState(room, state); - for (const name in this.getRoomCreeps(room)) { - if (!Game.creeps[name]) { - console.log(`Creep ${name} is dead, cleaning up memory.`); + state = CreepRunner.run(room, state); - const roleDefinition = CreepRoles[Memory.creeps[name].role as CreepRole]; - roleDefinition.handler.destroy(Memory.creeps[name], state); - delete Memory.creeps[name]; // Clean up memory for dead creeps - - continue; // Skip to the next creep - } - const creep = Game.creeps[name]; - - const roleDefinition = CreepRoles[creep.memory.role as CreepRole]; - if (!roleDefinition) { - console.log(`Creep ${creep.name} has an unknown role: ${creep.memory.role}`); - continue; - } - - state = roleDefinition.handler.run(creep, state); - } - - for (const spawn of room.find(FIND_MY_SPAWNS)) { - this.validateSpawnState(spawn, state); - } - - return state; - } - - private static getRoomCreeps(room: Room): Record { - return Object.keys(Memory.creeps) - .filter(name => Memory.creeps[name].room === room.name) - .reduce((creeps: Record, creepName: string) => { - creeps[creepName] = Memory.creeps[creepName]; - return creeps; - }, {}); - } - - private static updateSpawnState(room: Room, state: GameState) { - const sources = room.find(FIND_SOURCES); - if (!state.sourcesStates) { - state.sourcesStates = {}; - state.maxHarvesters = 0; - } - for (const source of sources) { - const sourceId = source.id.toString(); - - if (!state.sourcesStates[sourceId]) { - state.sourcesStates[sourceId] = { - id: sourceId, - pos: source.pos, - spots: createSourcePositionMatrix() - }; - forEachMatrixSpot(state.sourcesStates[sourceId].spots, (delta, status) => { - if (status !== PositionSpotStatus.UNKNOWN) { - return; // Skip known spots - } - const pos = getPositionWithDelta(source.pos, delta); - if (checkPositionWalkable(pos)) { - setSpotStatus(state.sourcesStates[sourceId].spots, delta, PositionSpotStatus.EMPTY); - state.maxHarvesters = state.maxHarvesters + 1; - } else { - setSpotStatus(state.sourcesStates[sourceId].spots, delta, PositionSpotStatus.INVALID); - } - }); - } - } - } - - private static validateSpawnState(spawn: StructureSpawn, state: GameState) { - if (spawn.spawning) { - // console.log(`Spawn ${this.spawn.name} is currently spawning a creep.`); - return; - } - - const creepRequisition = this.checksNeedsCreeps(spawn.room); - if (Object.values(creepRequisition).every(count => count <= 0)) { - // console.log(`Spawn ${this.spawn.name} has no creep needs.`); - return; - } - - const totalCreeps = Object.values(Game.creeps).length; - if (totalCreeps >= state.maxHarvesters) { - return; - } - - const rolesToSpawn = this.sortCreepRolesByPriority(creepRequisition); - - for (const role of rolesToSpawn) { - if (spawn.store[RESOURCE_ENERGY] < get_role_cost(role)) { - continue; - } - - const newName = `${role.name}_${Game.time}`; - const spawnResult = spawn.spawnCreep(role.body, newName, { - memory: { - role: role.name, - room: spawn.room.name, - spawnId: spawn.id, - working: false - } - }); - if (spawnResult === OK) { - console.log(`Spawn ${spawn.name} successfully spawned a new ${role.name}: ${newName}.`); - return; // Exit after spawning one creep - } else { - console.error(`Spawn ${spawn.name} failed to spawn a new ${role.name}: ${spawnResult}`); - } - } - } - - private static checksNeedsCreeps(room: Room): CreepRequisition { - const creepCounts: Record = {}; - for (const creepMemory of Object.values(this.getRoomCreeps(room))) { - const role = creepMemory.role; - creepCounts[role] = (creepCounts[role] || 0) + 1; - } - - const requisition: CreepRequisition = { - harvester: 0, - upgrader: 0, - builder: 0 - }; - for (const role in DEFAULT_GAME_CONFIG.minCreepsPerRole) { - if (!(role in CreepRoles)) { - console.log(`Unknown creep role: ${role}`); - continue; - } - const roleType = role as CreepRole; - requisition[roleType] = DEFAULT_GAME_CONFIG.minCreepsPerRole[roleType] - (creepCounts[role] || 0); - - if (requisition[roleType] < 0) { - requisition[roleType] = 0; // Ensure we don't have negative requisitions - } - } - - return requisition; - } - - private static sortCreepRolesByPriority(requisition: CreepRequisition): RoleDefinition[] { - return Object.keys(requisition) - .filter(role => requisition[role as CreepRole] > 0) - .map(role => CreepRoles[role as CreepRole]) - .sort((a, b) => a.priority - b.priority); + return RequisitionsManager.validateState(room, state); } } diff --git a/src/utils/funcs/getRoomCreeps.ts b/src/utils/funcs/getRoomCreeps.ts new file mode 100644 index 0000000..626ce1b --- /dev/null +++ b/src/utils/funcs/getRoomCreeps.ts @@ -0,0 +1,8 @@ +export const getRoomCreeps = (room: Room): Record => { + return Object.keys(Memory.creeps) + .filter(name => Memory.creeps[name].room === room.name) + .reduce((creeps: Record, creepName: string) => { + creeps[creepName] = Memory.creeps[creepName]; + return creeps; + }, {}); +}; diff --git a/src/utils/funcs/sortCreepRolesByPriority.ts b/src/utils/funcs/sortCreepRolesByPriority.ts new file mode 100644 index 0000000..c192297 --- /dev/null +++ b/src/utils/funcs/sortCreepRolesByPriority.ts @@ -0,0 +1,8 @@ +import { CreepRequisition, CreepRole, CreepRoles, RoleDefinition } from "types/creeps"; + +export const sortCreepRolesByPriority = (requisition: CreepRequisition): RoleDefinition[] => { + return Object.keys(requisition) + .filter(role => requisition[role as CreepRole] > 0) + .map(role => CreepRoles[role as CreepRole]) + .sort((a, b) => a.priority - b.priority); +}; diff --git a/tsconfig.json b/tsconfig.json index a4c834f..056f67a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,6 @@ "allowSyntheticDefaultImports": true, "types": ["screeps"] }, - "include": ["src/**/*"], + "include": ["src/**/*", "src/RequisitionsManager.ts"], "exclude": ["node_modules", "dist"] }