diff --git a/eslint.config.js b/eslint.config.js index af13e99..b12f334 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -38,6 +38,14 @@ module.exports = defineConfig([{ "rules": { "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], }, }, { languageOptions: { @@ -50,4 +58,4 @@ module.exports = defineConfig([{ }, files: ["**/.eslintrc.{js,cjs}"], -}]); \ No newline at end of file +}]); diff --git a/src/RoomRunner.ts b/src/RoomRunner.ts index 277fc86..4e722bc 100644 --- a/src/RoomRunner.ts +++ b/src/RoomRunner.ts @@ -1,14 +1,20 @@ 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/checkPosition"; import { get_role_cost as get_role_cost } from "utils/funcs/getRoleCost"; +import { + createSourcePositionMatrix, + forEachMatrixSpot, + getPositionWithDelta, + PositionSpotStatus, + setSpotStatus +} from "utils/positions"; class RoomRunner { public static run(room: Room, state: GameState): GameState { this.updateSpawnState(room, state); - for(const name in this.getRoomCreeps(room)) { + for (const name in this.getRoomCreeps(room)) { if (!Game.creeps[name]) { console.log(`Creep ${name} is dead, cleaning up memory.`); @@ -49,29 +55,29 @@ class RoomRunner { const sources = room.find(FIND_SOURCES); if (!state.sourcesStates) { state.sourcesStates = {}; - state.maxHarvesters = 0 + 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(), + id: sourceId, + pos: source.pos, + spots: createSourcePositionMatrix() }; forEachMatrixSpot(state.sourcesStates[sourceId].spots, (delta, status) => { - if (status !== SourceSpotStatus.UNKNOWN) { + if (status !== PositionSpotStatus.UNKNOWN) { return; // Skip known spots } const pos = getPositionWithDelta(source.pos, delta); if (checkPositionWalkable(pos)) { - setSpotStatus(state.sourcesStates[sourceId].spots, delta, SourceSpotStatus.EMPTY); + setSpotStatus(state.sourcesStates[sourceId].spots, delta, PositionSpotStatus.EMPTY); state.maxHarvesters = state.maxHarvesters + 1; } else { - setSpotStatus(state.sourcesStates[sourceId].spots, delta, SourceSpotStatus.INVALID); + setSpotStatus(state.sourcesStates[sourceId].spots, delta, PositionSpotStatus.INVALID); } - }) + }); } } } diff --git a/src/main.ts b/src/main.ts index 73a4a62..cdd4317 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { CreepDestination } from "types/creeps"; -import { SourcePositionMatrix, SourceSpotStatus } from "./types/source"; import RoomRunner from "RoomRunner"; +import { PositionMatrix } from "utils/positions"; declare global { /* @@ -13,7 +13,7 @@ declare global { */ interface SourceState { id: string; - spots: SourcePositionMatrix; + spots: PositionMatrix; pos: RoomPosition; } @@ -46,13 +46,13 @@ declare global { } } - export const loop = () => { Memory.roomStateRegistry = Memory.roomStateRegistry || {}; for (const roomName of Object.keys(Game.rooms)) { Memory.roomStateRegistry[roomName] = RoomRunner.run( - Game.rooms[roomName], Memory.roomStateRegistry[roomName] || {} + Game.rooms[roomName], + Memory.roomStateRegistry[roomName] || {} ); } }; diff --git a/src/roleHandlers/BaseHandler.interface.ts b/src/roleHandlers/BaseHandler.interface.ts index 055e5c2..a47b3dd 100644 --- a/src/roleHandlers/BaseHandler.interface.ts +++ b/src/roleHandlers/BaseHandler.interface.ts @@ -1,4 +1,12 @@ export abstract class RoleHandler { - static destroy(creepMemory: CreepMemory, state: GameState): void {}; - static run(creep: Creep, state: GameState): GameState {}; + public static destroy(_creepMemory: CreepMemory, _state: GameState): void { + // Default implementation does nothing + // Subclasses should override this method + } + + public static run(_creep: Creep, state: GameState): GameState { + // Default implementation returns state unchanged + // Subclasses should override this method + return state; + } } diff --git a/src/roleHandlers/harvester.handler.ts b/src/roleHandlers/harvester.handler.ts index 3e3307e..27eb10e 100644 --- a/src/roleHandlers/harvester.handler.ts +++ b/src/roleHandlers/harvester.handler.ts @@ -1,9 +1,7 @@ import { getSourceById, getSpawnById } from "utils/funcs/getById"; import { RoleHandler } from "./BaseHandler.interface"; -import { getNextEmptySpot, getPositionWithDelta, setSpotStatus, SourceSpotStatus } from "types/source"; import { SourceDestination } from "types/creeps"; - - +import { getNextEmptySpot, getPositionWithDelta, PositionSpotStatus, setSpotStatus } from "utils/positions"; class HarvesterHandler extends RoleHandler { public static destroy(creepMemory: CreepMemory, state: GameState): void { @@ -40,7 +38,7 @@ class HarvesterHandler extends RoleHandler { setSpotStatus( state.sourcesStates[creep.memory.previousDestination.id].spots, creep.memory.previousDestination.sourceSpot, - SourceSpotStatus.EMPTY + PositionSpotStatus.EMPTY ); delete creep.memory.previousDestination; } @@ -80,23 +78,19 @@ class HarvesterHandler extends RoleHandler { continue; // No empty spots available, skip to next source } - setSpotStatus( - sourceState.spots, - emptySpot, - SourceSpotStatus.OCCUPIED - ); + setSpotStatus(sourceState.spots, emptySpot, PositionSpotStatus.OCCUPIED); creep.memory.destination = { id: source.id, type: "source", sourceSpot: emptySpot }; - return + return; } console.log(`Creep ${creep.name} could not find a valid source.`); } - private static onSourceDestination(creep: Creep, state: GameState) { + private static onSourceDestination(creep: Creep, _state: GameState) { if (!creep.memory.destination || creep.memory.destination.type !== "source") { console.log(`Creep ${creep.name} has no valid destination set.`); delete creep.memory.destination; @@ -110,19 +104,20 @@ class HarvesterHandler extends RoleHandler { } if (creep.harvest(source) === ERR_NOT_IN_RANGE) { - const sourceSpotPosition = getPositionWithDelta( - source.pos, creep.memory.destination.sourceSpot - ) - creep.moveTo(sourceSpotPosition, { reusePath: 10, visualizePathStyle: { stroke: '#ffffff', lineStyle: 'dashed', strokeWidth: 0.1 } }); + const sourceSpotPosition = getPositionWithDelta(source.pos, creep.memory.destination.sourceSpot); + creep.moveTo(sourceSpotPosition, { + reusePath: 10, + visualizePathStyle: { stroke: "#ffffff", lineStyle: "dashed", strokeWidth: 0.1 } + }); } } - private static onSpawnDestination(creep: Creep, state: GameState) { + private static onSpawnDestination(creep: Creep, _state: GameState) { if (creep.memory.destination === undefined) { creep.memory.destination = { id: creep.memory.spawnId, type: "spawn" - } + }; } const spawn = getSpawnById(creep.memory.destination.id); @@ -132,12 +127,13 @@ class HarvesterHandler extends RoleHandler { } if (creep.transfer(spawn, RESOURCE_ENERGY) === ERR_NOT_IN_RANGE) { - creep.moveTo(spawn, { reusePath: 10, visualizePathStyle: { stroke: '#ffffff', lineStyle: 'dashed', strokeWidth: 0.1 } }); + creep.moveTo(spawn, { + reusePath: 10, + visualizePathStyle: { stroke: "#ffffff", lineStyle: "dashed", strokeWidth: 0.1 } + }); } } - - private static findClosestSource(creep: Creep, state: GameState): Source[] { const sources = Object.keys(state.sourcesStates) .map(sourceId => getSourceById(sourceId)) @@ -158,16 +154,8 @@ class HarvesterHandler extends RoleHandler { return; } - setSpotStatus( - sourceState.spots, - destination.sourceSpot, - SourceSpotStatus.EMPTY - ); + setSpotStatus(sourceState.spots, destination.sourceSpot, PositionSpotStatus.EMPTY); } } - - - - export default HarvesterHandler; diff --git a/src/roleHandlers/index.ts b/src/roleHandlers/index.ts index 638e512..9f96bd2 100644 --- a/src/roleHandlers/index.ts +++ b/src/roleHandlers/index.ts @@ -3,7 +3,4 @@ import { RoleHandler } from "./BaseHandler.interface"; import HarvesterHandler from "./harvester.handler"; import UpgraderHandler from "./upgrader.handler"; - - - export { RoleHandler, HarvesterHandler, UpgraderHandler }; diff --git a/src/roleHandlers/upgrader.handler.ts b/src/roleHandlers/upgrader.handler.ts index 9573a07..c67ec97 100644 --- a/src/roleHandlers/upgrader.handler.ts +++ b/src/roleHandlers/upgrader.handler.ts @@ -1,8 +1,7 @@ import { getControllerById, getSourceById } from "utils/funcs/getById"; import { RoleHandler } from "./BaseHandler.interface"; -import { getNextEmptySpot, getPositionWithDelta, setSpotStatus, SourceSpotStatus } from "types/source"; - - +import { getNextEmptySpot, PositionSpotStatus, setSpotStatus, getPositionWithDelta } from "utils/positions"; +import { SourceDestination } from "types/creeps"; class UpgraderHandler extends RoleHandler { public static destroy(creepMemory: CreepMemory, state: GameState): void { @@ -39,7 +38,7 @@ class UpgraderHandler extends RoleHandler { setSpotStatus( state.sourcesStates[creep.memory.previousDestination.id].spots, creep.memory.previousDestination.sourceSpot, - SourceSpotStatus.EMPTY + PositionSpotStatus.EMPTY ); delete creep.memory.previousDestination; } @@ -86,23 +85,19 @@ class UpgraderHandler extends RoleHandler { continue; // No empty spots available, skip to next source } - setSpotStatus( - sourceState.spots, - emptySpot, - SourceSpotStatus.OCCUPIED - ); + setSpotStatus(sourceState.spots, emptySpot, PositionSpotStatus.OCCUPIED); creep.memory.destination = { id: source.id, type: "source", sourceSpot: emptySpot }; - return + return; } console.log(`Creep ${creep.name} could not find a valid source.`); } - private static onSourceDestination(creep: Creep, state: GameState) { + private static onSourceDestination(creep: Creep, _state: GameState) { if (!creep.memory.destination || creep.memory.destination.type !== "source") { console.log(`Creep ${creep.name} has no valid destination set.`); delete creep.memory.destination; @@ -116,14 +111,15 @@ class UpgraderHandler extends RoleHandler { } if (creep.harvest(source) === ERR_NOT_IN_RANGE) { - const sourceSpotPosition = getPositionWithDelta( - source.pos, creep.memory.destination.sourceSpot - ) - creep.moveTo(sourceSpotPosition, { reusePath: 10, visualizePathStyle: { stroke: '#ffffff', lineStyle: 'dashed', strokeWidth: 0.1 } }); + const sourceSpotPosition = getPositionWithDelta(source.pos, creep.memory.destination.sourceSpot); + creep.moveTo(sourceSpotPosition, { + reusePath: 10, + visualizePathStyle: { stroke: "#ffffff", lineStyle: "dashed", strokeWidth: 0.1 } + }); } } - private static onControllerDestination(creep: Creep, state: GameState) { + private static onControllerDestination(creep: Creep, _state: GameState) { if (creep.memory.destination === undefined) { if (!creep.room.controller) { console.log(`Creep ${creep.name} has no valid controller to upgrade.`); @@ -133,7 +129,7 @@ class UpgraderHandler extends RoleHandler { creep.memory.destination = { id: creep.room.controller.id, type: "controller" - } + }; } const controller = getControllerById(creep.memory.destination.id); @@ -143,12 +139,13 @@ class UpgraderHandler extends RoleHandler { } if (creep.upgradeController(controller) === ERR_NOT_IN_RANGE) { - creep.moveTo(controller, { reusePath: 10, visualizePathStyle: { stroke: '#ffffff', lineStyle: 'dashed', strokeWidth: 0.1 } }); + creep.moveTo(controller, { + reusePath: 10, + visualizePathStyle: { stroke: "#ffffff", lineStyle: "dashed", strokeWidth: 0.1 } + }); } } - - private static findClosestSource(creep: Creep, state: GameState): Source[] { const sources = Object.keys(state.sourcesStates) .map(sourceId => getSourceById(sourceId)) @@ -169,16 +166,8 @@ class UpgraderHandler extends RoleHandler { return; } - setSpotStatus( - sourceState.spots, - destination.sourceSpot, - SourceSpotStatus.EMPTY - ); + setSpotStatus(sourceState.spots, destination.sourceSpot, PositionSpotStatus.EMPTY); } } - - - - export default UpgraderHandler; diff --git a/src/types/creeps.ts b/src/types/creeps.ts index 30f3a6b..bd75ae9 100644 --- a/src/types/creeps.ts +++ b/src/types/creeps.ts @@ -1,5 +1,5 @@ import { HarvesterHandler, RoleHandler, UpgraderHandler } from "roleHandlers"; -import { PositionDelta } from "./source"; +import { PositionDelta } from "utils/positions"; export type RoleDefinition = { name: string; @@ -33,35 +33,29 @@ 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 ControllerDestination = { id: string; // ID of the controller type: "controller"; -} +}; export type ConstructionSiteDestination = { id: string; // ID of the construction site type: "constructionSite"; -} - - -export type CreepDestination = ( - SpawnDestination | - SourceDestination | - ControllerDestination | - ConstructionSiteDestination -); +}; +export type CreepDestination = + | SpawnDestination + | SourceDestination + | ControllerDestination + | ConstructionSiteDestination; diff --git a/src/types/source.ts b/src/types/source.ts deleted file mode 100644 index 3bd5016..0000000 --- a/src/types/source.ts +++ /dev/null @@ -1,81 +0,0 @@ -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 = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; - -export type SourcePositionMatrix = { - locked: boolean; - data: [ - SourceSpotStatus, SourceSpotStatus, SourceSpotStatus, - SourceSpotStatus, SourceSpotStatus, SourceSpotStatus, - SourceSpotStatus, SourceSpotStatus, SourceSpotStatus - ]; -} - - -type MatrixPoint = { - x: PositionDeltaValue; - y: PositionDeltaValue; -} - - -const indexToMatrixPoint = (index: number): MatrixPoint => { - // where the 0,0 point is the center of the matrix and -1, -1 is the top-left corner - const x = ((index % 3) - 1) as PositionDeltaValue; // Convert index to x coordinate (-1, 0, 1) - const y = (Math.floor(index / 3) - 1) as PositionDeltaValue; // Convert index to y coordinate (-1, 0, 1) - return { x, y }; -} - -export const createSourcePositionMatrix = () : SourcePositionMatrix => { - return { - locked: false, - data: [ - 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.data.findIndex( status => status === SourceSpotStatus.EMPTY); - - if (index === -1) { - return null; // No empty spot found - } - - return index as PositionDelta; // Convert index to PositionDelta -}; - - -export const setSpotStatus = (matrix: SourcePositionMatrix, delta: PositionDelta, status: SourceSpotStatus): void => { - matrix.data[delta as number] = status; -} - - -export const getPositionWithDelta = (pos: RoomPosition, delta: PositionDelta): RoomPosition => { - const matrixPoint = indexToMatrixPoint(delta as number); - return new RoomPosition( - pos.x + matrixPoint.x, - pos.y + matrixPoint.y, - pos.roomName - ); -} - -export const forEachMatrixSpot = (matrix: SourcePositionMatrix, callback: (delta: PositionDelta, status: SourceSpotStatus) => void): void => { - for (const index in matrix.data) { - callback(index as PositionDelta, matrix.data[index]); - } -}; diff --git a/src/utils/funcs/checkPosition.ts b/src/utils/funcs/checkPosition.ts index 7fe20ae..24fa221 100644 --- a/src/utils/funcs/checkPosition.ts +++ b/src/utils/funcs/checkPosition.ts @@ -1,5 +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' -} + return terrain.length === 0 || terrain[0] !== "wall"; +}; diff --git a/src/utils/funcs/getById.ts b/src/utils/funcs/getById.ts index a14a7ee..1737ae8 100644 --- a/src/utils/funcs/getById.ts +++ b/src/utils/funcs/getById.ts @@ -11,8 +11,7 @@ export const getSourceById = (sourceId: string): Source | null => { } return source; -} - +}; export const getSpawnById = (spawnId: string): StructureSpawn | null => { if (!spawnId) { @@ -27,8 +26,7 @@ export const getSpawnById = (spawnId: string): StructureSpawn | null => { } return spawn; -} - +}; export const getControllerById = (controllerId: string): StructureController | null => { if (!controllerId) { @@ -43,4 +41,4 @@ export const getControllerById = (controllerId: string): StructureController | n } return controller; -} +}; diff --git a/src/utils/positions/index.ts b/src/utils/positions/index.ts new file mode 100644 index 0000000..b4747af --- /dev/null +++ b/src/utils/positions/index.ts @@ -0,0 +1,21 @@ +import { PositionSpotStatus, PositionDeltaValue, PositionMatrix, PositionDelta } from "./position.types"; + +import { + createSourcePositionMatrix, + forEachMatrixSpot, + getNextEmptySpot, + setSpotStatus, + getPositionWithDelta +} from "./position.funcs"; + +export { + PositionSpotStatus, + PositionDeltaValue, + PositionMatrix, + PositionDelta, + createSourcePositionMatrix, + forEachMatrixSpot, + getNextEmptySpot, + setSpotStatus, + getPositionWithDelta +}; diff --git a/src/utils/positions/position.funcs.ts b/src/utils/positions/position.funcs.ts new file mode 100644 index 0000000..75fe126 --- /dev/null +++ b/src/utils/positions/position.funcs.ts @@ -0,0 +1,88 @@ +import { PositionDelta, PositionDeltaValue, PositionMatrix, PositionSpotStatus } from "./position.types"; + +/** + * Represents a point in the matrix with x and y coordinates. + */ +type MatrixPoint = { + x: PositionDeltaValue; + y: PositionDeltaValue; +}; + +/** + * Converts a matrix index to a matrix point. + */ +const indexToMatrixPoint = (index: number): MatrixPoint => { + // where the 0,0 point is the center of the matrix and -1, -1 is the top-left corner + const x = ((index % 3) - 1) as PositionDeltaValue; // Convert index to x coordinate (-1, 0, 1) + const y = (Math.floor(index / 3) - 1) as PositionDeltaValue; // Convert index to y coordinate (-1, 0, 1) + return { x, y }; +}; + +/** + * Creates a source position matrix with a default value. + * The center of the matrix is always `PositionSpotStatus.CENTER`. + * The default value is used for all other spots. + */ +export const createSourcePositionMatrix = (default_value?: PositionSpotStatus): PositionMatrix => { + const center_value = PositionSpotStatus.CENTER; + + default_value = default_value || PositionSpotStatus.UNKNOWN; + + return [ + default_value, + default_value, + default_value, + default_value, + center_value, + default_value, + default_value, + default_value, + default_value + ]; +}; + +/** + * Gets the next empty spot in the matrix. + */ +export const getNextEmptySpot = (matrix: PositionMatrix): PositionDelta | null => { + const index = matrix.findIndex(status => status === PositionSpotStatus.EMPTY); + + if (index === -1) { + return null; // No empty spot found + } + + return index as PositionDelta; // Convert index to PositionDelta +}; + +/** + * Sets the status of a spot in the matrix. + * Throws an error if the delta is out of bounds. + */ +export const setSpotStatus = (matrix: PositionMatrix, delta: PositionDelta, status: PositionSpotStatus): void => { + if (delta < 0 || delta >= matrix.length) { + throw new Error(`Invalid delta: ${delta}. Must be between 0 and ${matrix.length - 1}.`); + } + matrix[delta] = status; +}; + +/** + * Gets the position with a delta applied to the given RoomPosition. + */ +export const getPositionWithDelta = (pos: RoomPosition, delta: PositionDelta): RoomPosition => { + const matrixPoint = indexToMatrixPoint(delta as number); + return new RoomPosition(pos.x + matrixPoint.x, pos.y + matrixPoint.y, pos.roomName); +}; + +/** + * Iterates over each spot in the matrix and applies the callback function. + * The callback receives the delta (index) and the status of the spot. + */ +export const forEachMatrixSpot = ( + matrix: PositionMatrix, + callback: (delta: PositionDelta, status: PositionSpotStatus) => void +): void => { + for (const index in matrix) { + const positionIndex = Number(index); + callback(positionIndex as PositionDelta, matrix[positionIndex]); + } +}; diff --git a/src/utils/positions/position.types.ts b/src/utils/positions/position.types.ts new file mode 100644 index 0000000..eaf192d --- /dev/null +++ b/src/utils/positions/position.types.ts @@ -0,0 +1,37 @@ +/** + * Represents the status of a position spot + */ +export const PositionSpotStatus = { + INVALID: "-2", + CENTER: "-1", + UNKNOWN: "0", + EMPTY: "1", + OCCUPIED: "2" +} as const; + +export type PositionSpotStatus = (typeof PositionSpotStatus)[keyof typeof PositionSpotStatus]; + +/** + * Represents dislocation to be applied a axis to a position relative to a source or a point of interest. + */ +export type PositionDeltaValue = -1 | 0 | 1; + +/** + * Represents a matrix of positions around a source or a point of interest. + * The matrix is a 3x3 grid where the center is the point of interest. + * The center should always be `PositionSpotStatus.CENTER`. + */ +export type PositionMatrix = [ + PositionSpotStatus, + PositionSpotStatus, + PositionSpotStatus, + PositionSpotStatus, + PositionSpotStatus, + PositionSpotStatus, + PositionSpotStatus, + PositionSpotStatus, + PositionSpotStatus +]; + +// Valid indices for the 3x3 matrix (0-8) +export type PositionDelta = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;