diff --git a/src/main.ts b/src/main.ts index eb3f024..69366d0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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") diff --git a/src/roleHandlers/harvesterHandler.ts b/src/roleHandlers/harvesterHandler.ts index 6af0a8b..565d4d0 100644 --- a/src/roleHandlers/harvesterHandler.ts +++ b/src/roleHandlers/harvesterHandler.ts @@ -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)); } } diff --git a/src/spawnHandler.ts b/src/spawnHandler.ts index ca7dc94..8847ac3 100644 --- a/src/spawnHandler.ts +++ b/src/spawnHandler.ts @@ -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 } }); diff --git a/src/types/creeps.ts b/src/types/creeps.ts index 5fbe1a7..f0280d6 100644 --- a/src/types/creeps.ts +++ b/src/types/creeps.ts @@ -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; + + + + +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; + diff --git a/src/types/gameConfig.ts b/src/types/gameConfig.ts index b8c4878..9d1ea62 100644 --- a/src/types/gameConfig.ts +++ b/src/types/gameConfig.ts @@ -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 } diff --git a/src/types/source.ts b/src/types/source.ts new file mode 100644 index 0000000..57d091c --- /dev/null +++ b/src/types/source.ts @@ -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]); + } + } +}; diff --git a/src/utils/funcs/check_position.ts b/src/utils/funcs/check_position.ts new file mode 100644 index 0000000..7fe20ae --- /dev/null +++ b/src/utils/funcs/check_position.ts @@ -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' +} diff --git a/src/utils/funcs/get_by_id.ts b/src/utils/funcs/get_by_id.ts new file mode 100644 index 0000000..7c23dab --- /dev/null +++ b/src/utils/funcs/get_by_id.ts @@ -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(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(spawnId); + if (!spawn) { + console.warn(`No spawn found with ID: ${spawnId}`); + return null; + } + + return spawn; +} diff --git a/src/utils/funcs/get_source_by_id.ts b/src/utils/funcs/get_source_by_id.ts deleted file mode 100644 index e05082e..0000000 --- a/src/utils/funcs/get_source_by_id.ts +++ /dev/null @@ -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(sourceId); - if (!source) { - console.warn(`No source found with ID: ${sourceId}`); - return null; - } - - return source; -} - - -export default getSourceById;