Spawn Room Detection

This commit is contained in:
2025-07-07 20:05:13 -03:00
parent 4f28d240e0
commit c2a9df5e77
9 changed files with 225 additions and 61 deletions

View File

@@ -1,3 +1,5 @@
import { CreepDestination } from "types/creeps";
import { SourcePositionMatrix, SourceSpotStatus } from "./types/source";
import SpawnHandler from "spawnHandler";
declare global {
@@ -11,8 +13,8 @@ declare global {
*/
interface SourceState {
id: string;
spots: SourcePositionMatrix;
pos: RoomPosition;
maxHarvesters: number|null;
currentHarvesters: number;
}
@@ -30,7 +32,9 @@ declare global {
interface CreepMemory {
role: string;
room: string;
spawnId: string;
working: boolean;
destination?: CreepDestination;
}
// Syntax for adding proprties to `global` (ex "global.log")

View File

@@ -1,50 +1,79 @@
import getSourceById from "utils/funcs/get_source_by_id";
import { getSourceById, getSpawnById } from "utils/funcs/get_by_id";
import { RoleHandler } from "./roleHandler.interface";
import { getNextEmptySpot } from "types/source";
class HarvesterHandler extends RoleHandler {
public static run(creep: Creep, state: GameState): GameState {
const source = this.findClosestSource(creep, state);
console.log(`Running HarvesterHandler for creep: ${creep.name}`);
if (creep.store.getFreeCapacity() > 0) {
if (creep.harvest(source) === ERR_NOT_IN_RANGE) {
creep.moveTo(source, { reusePath: true });
}
} else {
const spawn = Game.spawns['Spawn1'];
if (creep.transfer(spawn, RESOURCE_ENERGY) === ERR_NOT_IN_RANGE) {
creep.moveTo(spawn, { reusePath: true });
}
switch (creep.memory.destination?.type) {
case "spawn":
this.onSpawnDestination(creep, state);
break;
default:
this.onSourceDestination(creep, state);
break;
}
return state;
}
private static findClosestSource(creep: Creep, state: GameState): Source|null {
for (const source of creep.room.find(FIND_SOURCES)) {
}
let closestSourceId = null;
for (const sourceId in state.sourcesStates) {
if (!closestSourceId) {
closestSourceId = sourceId;
}
const sourceInfo = state.sourcesStates[sourceId];
const creepPos = creep.pos;
if (creepPos.getRangeTo(sourceInfo["pos"]) < creepPos.getRangeTo(state.sourcesStates[closestSourceId]["pos"])) {
closestSourceId = sourceId;
private static onSourceDestination(creep: Creep, state: GameState) {
if (!creep.memory.destination) {
const sources = this.findClosestSource(creep, state);
if (sources.length > 0) {
creep.memory.destination = {
id: sources[0].id,
type: "source",
sourceSpot: getNextEmptySpot(state.sourcesStates[sources[0].id].spots) || { x: 0, y: 0 }
};
} else {
console.warn(`No sources found for creep: ${creep.name}`);
return;
}
}
if (!closestSourceId) {
console.warn(`No sources found for creep: ${creep.name}`);
return null;
const source = getSourceById(creep.memory.destination.id);
if (!source) {
console.warn(`Source not found for creep: ${creep.name}`);
return;
}
return getSourceById(closestSourceId);
if (creep.harvest(source) === ERR_NOT_IN_RANGE) {
creep.moveTo(source, { reusePath: true });
}
}
private static onSpawnDestination(creep: Creep, state: GameState) {
if (!creep.memory.destination) {
creep.memory.destination = {
id: creep.memory.spawnId,
type: "spawn"
}
}
if (creep.store.getFreeCapacity() <= 0) {
delete creep.memory.destination;
return;
}
const spawn = getSpawnById(creep.memory.destination.id);
if (!spawn) {
console.warn(`Spawn not found for creep: ${creep.name}`);
return;
}
if (creep.transfer(spawn, RESOURCE_ENERGY) === ERR_NOT_IN_RANGE) {
creep.moveTo(spawn, { reusePath: true });
}
}
private static findClosestSource(creep: Creep, state: GameState): Source[] {
return Object.keys(state.sourcesStates)
.map(sourceId => getSourceById(sourceId))
.filter(source => source !== null)
.sort((a, b) => creep.pos.getRangeTo(a) - creep.pos.getRangeTo(b));
}
}

View File

@@ -1,5 +1,7 @@
import { CreepRequisition, CreepRole, CreepRoles, RoleDefinition } from "types/creeps";
import { DEFAULT_GAME_CONFIG } from "types/gameConfig";
import { createSourcePositionMatrix, forEachMatrixSpot, getPositionWithDelta, setSpotStatus, SourceSpotStatus } from "types/source";
import { checkPositionWalkable } from "utils/funcs/check_position";
import { get_role_const as get_role_cost } from "utils/funcs/get_role_const";
class SpawnHandler {
@@ -12,17 +14,17 @@ class SpawnHandler {
public run(state: GameState): GameState {
this.updateSpawnState(state);
for(const name in Game.creeps) {
const creep = Game.creeps[name];
// for(const name in Game.creeps) {
// const creep = Game.creeps[name];
const roleDefinition = CreepRoles[creep.memory.role as CreepRole];
if (!roleDefinition) {
console.warn(`Creep ${creep.name} has an unknown role: ${creep.memory.role}`);
continue;
}
// const roleDefinition = CreepRoles[creep.memory.role as CreepRole];
// if (!roleDefinition) {
// console.warn(`Creep ${creep.name} has an unknown role: ${creep.memory.role}`);
// continue;
// }
state = roleDefinition.handler.run(creep, state);
}
// state = roleDefinition.handler.run(creep, state);
// }
this.validateSpawnState();
@@ -40,9 +42,21 @@ class SpawnHandler {
state.sourcesStates[sourceId] = {
"id": sourceId,
"pos": source.pos,
"maxHarvesters": null,
"spots": createSourcePositionMatrix(),
"currentHarvesters": 0
};
forEachMatrixSpot(state.sourcesStates[sourceId].spots, (delta, status) => {
if (status !== SourceSpotStatus.UNKNOWN) {
return; // Skip known spots
}
const pos = getPositionWithDelta(source.pos, delta);
if (checkPositionWalkable(pos)) {
setSpotStatus(state.sourcesStates[sourceId].spots, delta, SourceSpotStatus.EMPTY);
} else {
setSpotStatus(state.sourcesStates[sourceId].spots, delta, SourceSpotStatus.INVALID);
}
})
}
}
}
@@ -78,6 +92,7 @@ class SpawnHandler {
memory: {
role: role.name,
room: this.spawn.room.name,
spawnId: this.spawn.id,
working: false
}
});

View File

@@ -1,4 +1,5 @@
import { HarvesterHandler, RoleHandler } from "roleHandlers";
import { PositionDelta } from "./source";
export type RoleDefinition = {
name: string;
@@ -31,3 +32,21 @@ export const CreepRoles = {
export type CreepRole = keyof typeof CreepRoles;
export type CreepRequisition = Record<CreepRole, number>;
export type SpawnDestination = {
id: string; // ID of the spawn
type: "spawn";
}
export type SourceDestination = {
id: string; // ID of the source
type: "source";
sourceSpot: PositionDelta; // Position delta for the source spot
}
export type CreepDestination = SpawnDestination | SourceDestination;

View File

@@ -22,9 +22,9 @@ export type GameConfig = {
* @type {GameConfig}
*/
export const DEFAULT_GAME_CONFIG: GameConfig = {
maxCreeps: 15,
maxCreeps: 1,
minCreepsPerRole: {
harvester: 15,
harvester: 1,
upgrader: 0,
builder: 0
}

79
src/types/source.ts Normal file
View File

@@ -0,0 +1,79 @@
export const SourceSpotStatus = {
INVALID: "-2",
CENTER: "-1",
UNKNOWN: "0",
EMPTY: "1",
OCCUPIED: "2",
} as const;
export type SourceSpotStatus = (typeof SourceSpotStatus)[keyof typeof SourceSpotStatus];
export type PositionDeltaValue = -1 | 0 | 1;
export type PositionDelta = {
x: PositionDeltaValue;
y: PositionDeltaValue;
}
export type SourcePositionMatrix = [
SourceSpotStatus, SourceSpotStatus, SourceSpotStatus,
SourceSpotStatus, SourceSpotStatus, SourceSpotStatus,
SourceSpotStatus, SourceSpotStatus, SourceSpotStatus
]
export const createSourcePositionMatrix = () : SourcePositionMatrix => {
return [
SourceSpotStatus.UNKNOWN, SourceSpotStatus.UNKNOWN, SourceSpotStatus.UNKNOWN,
SourceSpotStatus.UNKNOWN, SourceSpotStatus.CENTER, SourceSpotStatus.UNKNOWN,
SourceSpotStatus.UNKNOWN, SourceSpotStatus.UNKNOWN, SourceSpotStatus.UNKNOWN
];
}
export const getNextEmptySpot = (matrix: SourcePositionMatrix): PositionDelta | null => {
const index = matrix.findIndex( status => status === SourceSpotStatus.EMPTY || status === SourceSpotStatus.UNKNOWN);
if (index === -1) {
return null; // No empty spot found
}
return {
x: (index % 3 - 1) as PositionDeltaValue, // Convert index to x delta (-1, 0, 1)
y: (Math.floor(index / 3) - 1) as PositionDeltaValue // Convert index to y delta (-1, 0, 1)
};
};
export const setSpotStatus = (matrix: SourcePositionMatrix, delta: PositionDelta, status: SourceSpotStatus): void => {
const x = delta.x + 1; // Convert to index (0, 1, 2)
const y = delta.y + 1; // Convert to index (0, 1, 2)
if (x < 0 || x > 2 || y < 0 || y > 2) {
throw new Error("Invalid position delta for source position matrix.");
}
const index = y * 3 + x; // Calculate the index in the flat array
matrix[index] = status;
}
export const getPositionWithDelta = (pos: RoomPosition, delta: PositionDelta): RoomPosition => {
return new RoomPosition(
pos.x + delta.x,
pos.y + delta.y,
pos.roomName
);
}
export const forEachMatrixSpot = (matrix: SourcePositionMatrix, callback: (delta: PositionDelta, status: SourceSpotStatus) => void): void => {
for (let y = -1; y <= 1; y++) {
for (let x = -1; x <= 1; x++) {
const delta: PositionDelta = { x: x as PositionDeltaValue, y: y as PositionDeltaValue };
const index = (y + 1) * 3 + (x + 1); // Convert delta to index
callback(delta, matrix[index]);
}
}
};

View File

@@ -0,0 +1,5 @@
export const checkPositionWalkable = (pos: RoomPosition) => {
// Check if the position is not obstructed by a wall
const terrain = pos.lookFor(LOOK_TERRAIN);
return terrain.length === 0 || terrain[0] !== 'wall'
}

View File

@@ -0,0 +1,30 @@
export const getSourceById = (sourceId: string): Source | null => {
if (!sourceId) {
console.warn("getSourceById called with an empty or undefined sourceId.");
return null;
}
const source = Game.getObjectById<Source>(sourceId);
if (!source) {
console.warn(`No source found with ID: ${sourceId}`);
return null;
}
return source;
}
export const getSpawnById = (spawnId: string): StructureSpawn | null => {
if (!spawnId) {
console.warn("getSpawnById called with an empty or undefined spawnId.");
return null;
}
const spawn = Game.getObjectById<StructureSpawn>(spawnId);
if (!spawn) {
console.warn(`No spawn found with ID: ${spawnId}`);
return null;
}
return spawn;
}

View File

@@ -1,17 +0,0 @@
const getSourceById = (sourceId: string): Source | null => {
if (!sourceId) {
console.warn("getSourceById called with an empty or undefined sourceId.");
return null;
}
const source = Game.getObjectById<Source>(sourceId);
if (!source) {
console.warn(`No source found with ID: ${sourceId}`);
return null;
}
return source;
}
export default getSourceById;