feat: implement RoleHandler with abstract methods for destroy and run
This commit is contained in:
@@ -1,12 +0,0 @@
|
|||||||
export abstract class RoleHandler {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9
src/roleHandlers/base.handler.ts
Normal file
9
src/roleHandlers/base.handler.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export abstract class RoleHandler {
|
||||||
|
public static destroy(_creepMemory: CreepMemory, _state: GameState): void {
|
||||||
|
throw Error('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static run(_creep: Creep, _state: GameState): GameState {
|
||||||
|
throw Error('not implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RoleHandler } from './BaseHandler.interface';
|
import { RoleHandler } from './base.handler';
|
||||||
import { SourceDestination } from '@/types/creeps';
|
import { SourceDestination } from '@/types/creeps';
|
||||||
import { getSourceById, getSpawnById } from '@/utils/funcs/getById';
|
import { getSourceById, getSpawnById } from '@/utils/funcs/getById';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RoleHandler } from './BaseHandler.interface';
|
import { RoleHandler } from './base.handler';
|
||||||
import HarvesterHandler from './harvester.handler';
|
import HarvesterHandler from './harvester.handler';
|
||||||
import UpgraderHandler from './upgrader.handler';
|
import UpgraderHandler from './upgrader.handler';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RoleHandler } from './BaseHandler.interface';
|
import { RoleHandler } from './base.handler';
|
||||||
import { SourceDestination } from '@/types/creeps';
|
import { SourceDestination } from '@/types/creeps';
|
||||||
import { getControllerById, getSourceById } from '@/utils/funcs/getById';
|
import { getControllerById, getSourceById } from '@/utils/funcs/getById';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -119,6 +119,32 @@ describe('HarvesterHandler', () => {
|
|||||||
expect(memory.previousDestination).toBeUndefined();
|
expect(memory.previousDestination).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('releases both destination and previousDestination source spots when both are set', () => {
|
||||||
|
const { state, memory } = createFixture({
|
||||||
|
memory: {
|
||||||
|
destination: { type: 'source', id: 'src1', sourceSpot: 0 },
|
||||||
|
previousDestination: {
|
||||||
|
type: 'source',
|
||||||
|
id: 'src1',
|
||||||
|
sourceSpot: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
state.sourcesStates['src1'].spots[0] = PositionSpotStatus.OCCUPIED;
|
||||||
|
state.sourcesStates['src1'].spots[2] = PositionSpotStatus.OCCUPIED;
|
||||||
|
|
||||||
|
HarvesterHandler.destroy(memory, state);
|
||||||
|
|
||||||
|
expect(state.sourcesStates['src1'].spots[0]).toBe(
|
||||||
|
PositionSpotStatus.EMPTY
|
||||||
|
);
|
||||||
|
expect(state.sourcesStates['src1'].spots[2]).toBe(
|
||||||
|
PositionSpotStatus.EMPTY
|
||||||
|
);
|
||||||
|
expect(memory.destination).toBeUndefined();
|
||||||
|
expect(memory.previousDestination).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('does nothing when destination is not a source', () => {
|
it('does nothing when destination is not a source', () => {
|
||||||
const { state, memory } = createFixture({
|
const { state, memory } = createFixture({
|
||||||
memory: {
|
memory: {
|
||||||
@@ -142,6 +168,21 @@ describe('HarvesterHandler', () => {
|
|||||||
|
|
||||||
expect(() => HarvesterHandler.destroy(memory, state)).not.toThrow();
|
expect(() => HarvesterHandler.destroy(memory, state)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not throw when destination source ID is missing from state', () => {
|
||||||
|
const { state, memory } = createFixture({
|
||||||
|
memory: {
|
||||||
|
destination: {
|
||||||
|
type: 'source',
|
||||||
|
id: 'nonexistent',
|
||||||
|
sourceSpot: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => HarvesterHandler.destroy(memory, state)).not.toThrow();
|
||||||
|
expect(memory.destination).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateCreepMemory (via run)', () => {
|
describe('validateCreepMemory (via run)', () => {
|
||||||
@@ -185,7 +226,7 @@ describe('HarvesterHandler', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('clears spawn destination when energy is empty', () => {
|
it('clears spawn destination when energy is empty', () => {
|
||||||
const { state, creep, mockSpawn } = createFixture({
|
const { state, creep, mockSpawn, mockSource } = createFixture({
|
||||||
memory: {
|
memory: {
|
||||||
destination: { type: 'spawn', id: 'spawn1' },
|
destination: { type: 'spawn', id: 'spawn1' },
|
||||||
},
|
},
|
||||||
@@ -193,6 +234,8 @@ describe('HarvesterHandler', () => {
|
|||||||
storeCapacity: 50,
|
storeCapacity: 50,
|
||||||
});
|
});
|
||||||
mockSpawn();
|
mockSpawn();
|
||||||
|
// Explicitly return null so onFindNewSource finds nothing
|
||||||
|
mockSource(null);
|
||||||
|
|
||||||
HarvesterHandler.run(creep, state);
|
HarvesterHandler.run(creep, state);
|
||||||
|
|
||||||
@@ -224,6 +267,19 @@ describe('HarvesterHandler', () => {
|
|||||||
|
|
||||||
expect(creep.memory.destination).toBeUndefined();
|
expect(creep.memory.destination).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('marks the assigned source spot as OCCUPIED in state', () => {
|
||||||
|
const { state, creep, mockSource } = createFixture();
|
||||||
|
mockSource();
|
||||||
|
|
||||||
|
HarvesterHandler.run(creep, state);
|
||||||
|
|
||||||
|
const spotIndex = (creep.memory.destination as any)?.sourceSpot;
|
||||||
|
expect(spotIndex).toBeDefined();
|
||||||
|
expect(state.sourcesStates['src1'].spots[spotIndex]).toBe(
|
||||||
|
PositionSpotStatus.OCCUPIED
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('onSourceDestination', () => {
|
describe('onSourceDestination', () => {
|
||||||
@@ -258,6 +314,26 @@ describe('HarvesterHandler', () => {
|
|||||||
expect(creep.moveTo).toHaveBeenCalled();
|
expect(creep.moveTo).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('moves to the computed spot position (not source pos) when not in range', () => {
|
||||||
|
const { state, source, creep, mockSource } = createFixture({
|
||||||
|
memory: {
|
||||||
|
destination: { type: 'source', id: 'src1', sourceSpot: 0 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mockSource();
|
||||||
|
(creep.harvest as jest.Mock).mockReturnValue(
|
||||||
|
(global as any).ERR_NOT_IN_RANGE
|
||||||
|
);
|
||||||
|
|
||||||
|
HarvesterHandler.run(creep, state);
|
||||||
|
|
||||||
|
expect(creep.moveTo).toHaveBeenCalledWith(
|
||||||
|
// spot 0 → delta (-1,-1) from source pos (5,5) → (4,4)
|
||||||
|
expect.objectContaining({ x: 4, y: 4, roomName: 'W1N1' }),
|
||||||
|
expect.objectContaining({ reusePath: 10 })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('logs and returns when source is not found', () => {
|
it('logs and returns when source is not found', () => {
|
||||||
const { state, creep, mockSource } = createFixture({
|
const { state, creep, mockSource } = createFixture({
|
||||||
memory: {
|
memory: {
|
||||||
@@ -331,4 +407,15 @@ describe('HarvesterHandler', () => {
|
|||||||
expect(creep.transfer).not.toHaveBeenCalled();
|
expect(creep.transfer).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('run', () => {
|
||||||
|
it('returns the state object', () => {
|
||||||
|
const { state, creep, mockSource } = createFixture();
|
||||||
|
mockSource(null);
|
||||||
|
|
||||||
|
const result = HarvesterHandler.run(creep, state);
|
||||||
|
|
||||||
|
expect(result).toBe(state);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -125,6 +125,32 @@ describe('UpgraderHandler', () => {
|
|||||||
expect(memory.previousDestination).toBeUndefined();
|
expect(memory.previousDestination).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('releases both destination and previousDestination source spots when both are set', () => {
|
||||||
|
const { state, memory } = createFixture({
|
||||||
|
memory: {
|
||||||
|
destination: { type: 'source', id: 'src1', sourceSpot: 0 },
|
||||||
|
previousDestination: {
|
||||||
|
type: 'source',
|
||||||
|
id: 'src1',
|
||||||
|
sourceSpot: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
state.sourcesStates['src1'].spots[0] = PositionSpotStatus.OCCUPIED;
|
||||||
|
state.sourcesStates['src1'].spots[3] = PositionSpotStatus.OCCUPIED;
|
||||||
|
|
||||||
|
UpgraderHandler.destroy(memory, state);
|
||||||
|
|
||||||
|
expect(state.sourcesStates['src1'].spots[0]).toBe(
|
||||||
|
PositionSpotStatus.EMPTY
|
||||||
|
);
|
||||||
|
expect(state.sourcesStates['src1'].spots[3]).toBe(
|
||||||
|
PositionSpotStatus.EMPTY
|
||||||
|
);
|
||||||
|
expect(memory.destination).toBeUndefined();
|
||||||
|
expect(memory.previousDestination).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('does nothing when destination is not a source', () => {
|
it('does nothing when destination is not a source', () => {
|
||||||
const { state, memory } = createFixture({
|
const { state, memory } = createFixture({
|
||||||
memory: {
|
memory: {
|
||||||
@@ -148,6 +174,21 @@ describe('UpgraderHandler', () => {
|
|||||||
|
|
||||||
expect(() => UpgraderHandler.destroy(memory, state)).not.toThrow();
|
expect(() => UpgraderHandler.destroy(memory, state)).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not throw when destination source ID is missing from state', () => {
|
||||||
|
const { state, memory } = createFixture({
|
||||||
|
memory: {
|
||||||
|
destination: {
|
||||||
|
type: 'source',
|
||||||
|
id: 'nonexistent',
|
||||||
|
sourceSpot: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => UpgraderHandler.destroy(memory, state)).not.toThrow();
|
||||||
|
expect(memory.destination).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('validateCreepMemory (via run)', () => {
|
describe('validateCreepMemory (via run)', () => {
|
||||||
@@ -173,7 +214,7 @@ describe('UpgraderHandler', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('transitions from source to controller destination when energy is full', () => {
|
it('transitions from source to controller destination when energy is full', () => {
|
||||||
const { state, controller, creep, mockSource, mockController } =
|
const { state, controller, creep, mockController } =
|
||||||
createFixture({
|
createFixture({
|
||||||
memory: {
|
memory: {
|
||||||
destination: {
|
destination: {
|
||||||
@@ -186,7 +227,7 @@ describe('UpgraderHandler', () => {
|
|||||||
storeCapacity: 50,
|
storeCapacity: 50,
|
||||||
controllerInRoom: { id: 'ctrl1' } as StructureController,
|
controllerInRoom: { id: 'ctrl1' } as StructureController,
|
||||||
});
|
});
|
||||||
mockSource();
|
// getSourceById is not called in this path — only getControllerById is needed
|
||||||
mockController(controller);
|
mockController(controller);
|
||||||
|
|
||||||
UpgraderHandler.run(creep, state);
|
UpgraderHandler.run(creep, state);
|
||||||
@@ -230,6 +271,13 @@ describe('UpgraderHandler', () => {
|
|||||||
UpgraderHandler.run(creep, state);
|
UpgraderHandler.run(creep, state);
|
||||||
|
|
||||||
expect(creep.memory.destination).toBeUndefined();
|
expect(creep.memory.destination).toBeUndefined();
|
||||||
|
// previousDestination is set to the old source before checking room.controller,
|
||||||
|
// so the source spot can be released on the next tick
|
||||||
|
expect(creep.memory.previousDestination).toEqual({
|
||||||
|
type: 'source',
|
||||||
|
id: 'src1',
|
||||||
|
sourceSpot: 0,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -257,6 +305,19 @@ describe('UpgraderHandler', () => {
|
|||||||
|
|
||||||
expect(creep.memory.destination).toBeUndefined();
|
expect(creep.memory.destination).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('marks the assigned source spot as OCCUPIED in state', () => {
|
||||||
|
const { state, creep, mockSource } = createFixture();
|
||||||
|
mockSource();
|
||||||
|
|
||||||
|
UpgraderHandler.run(creep, state);
|
||||||
|
|
||||||
|
const spotIndex = (creep.memory.destination as any)?.sourceSpot;
|
||||||
|
expect(spotIndex).toBeDefined();
|
||||||
|
expect(state.sourcesStates['src1'].spots[spotIndex]).toBe(
|
||||||
|
PositionSpotStatus.OCCUPIED
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('onSourceDestination', () => {
|
describe('onSourceDestination', () => {
|
||||||
@@ -291,6 +352,26 @@ describe('UpgraderHandler', () => {
|
|||||||
expect(creep.moveTo).toHaveBeenCalled();
|
expect(creep.moveTo).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('moves to the computed spot position (not source pos) when not in range', () => {
|
||||||
|
const { state, creep, mockSource } = createFixture({
|
||||||
|
memory: {
|
||||||
|
destination: { type: 'source', id: 'src1', sourceSpot: 0 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mockSource();
|
||||||
|
(creep.harvest as jest.Mock).mockReturnValue(
|
||||||
|
(global as any).ERR_NOT_IN_RANGE
|
||||||
|
);
|
||||||
|
|
||||||
|
UpgraderHandler.run(creep, state);
|
||||||
|
|
||||||
|
expect(creep.moveTo).toHaveBeenCalledWith(
|
||||||
|
// spot 0 → delta (-1,-1) from source pos (5,5) → (4,4)
|
||||||
|
expect.objectContaining({ x: 4, y: 4, roomName: 'W1N1' }),
|
||||||
|
expect.objectContaining({ reusePath: 10 })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('logs and returns when source is not found', () => {
|
it('logs and returns when source is not found', () => {
|
||||||
const { state, creep, mockSource } = createFixture({
|
const { state, creep, mockSource } = createFixture({
|
||||||
memory: {
|
memory: {
|
||||||
@@ -365,4 +446,15 @@ describe('UpgraderHandler', () => {
|
|||||||
expect(creep.upgradeController).not.toHaveBeenCalled();
|
expect(creep.upgradeController).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('run', () => {
|
||||||
|
it('returns the state object', () => {
|
||||||
|
const { state, creep, mockSource } = createFixture();
|
||||||
|
mockSource(null);
|
||||||
|
|
||||||
|
const result = UpgraderHandler.run(creep, state);
|
||||||
|
|
||||||
|
expect(result).toBe(state);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user