Better RoomRunner

This commit is contained in:
2025-07-15 19:36:25 -03:00
parent fb689cc300
commit be66fe0822
7 changed files with 212 additions and 155 deletions

36
src/CreepRunner.ts Normal file
View File

@@ -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;

View File

@@ -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<string, number> = {};
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;

59
src/RoomInspector.ts Normal file
View File

@@ -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;

View File

@@ -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<string, CreepMemory> {
return Object.keys(Memory.creeps)
.filter(name => Memory.creeps[name].room === room.name)
.reduce((creeps: Record<string, CreepMemory>, 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<string, number> = {};
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);
}
}

View File

@@ -0,0 +1,8 @@
export const getRoomCreeps = (room: Room): Record<string, CreepMemory> => {
return Object.keys(Memory.creeps)
.filter(name => Memory.creeps[name].room === room.name)
.reduce((creeps: Record<string, CreepMemory>, creepName: string) => {
creeps[creepName] = Memory.creeps[creepName];
return creeps;
}, {});
};

View File

@@ -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);
};