diff --git a/src/Action/ActionController.ts b/src/Action/ActionController.ts index e417a92..cc16657 100644 --- a/src/Action/ActionController.ts +++ b/src/Action/ActionController.ts @@ -1,10 +1,11 @@ import { Scheduler } from '../Scheduler'; -import { ActionError, TryLaterError } from '../Errors'; +import { AbortTaskError, TryLaterError } from '../Errors'; import { grabActiveVillageId } from '../Page/VillageBlock'; import { aroundMinutes } from '../utils'; import { Args } from '../Queue/Args'; import { Task } from '../Queue/TaskProvider'; import { VillageStorage } from '../Storage/VillageStorage'; +import { VillageStateRepository } from '../VillageState'; const actionMap: { [name: string]: Function | undefined } = {}; @@ -12,29 +13,35 @@ export function registerAction(constructor: Function) { actionMap[constructor.name] = constructor; } -export function createActionHandler(name: string, scheduler: Scheduler): ActionController | undefined { +export function createActionHandler( + name: string, + scheduler: Scheduler, + villageStateRepository: VillageStateRepository +): ActionController | undefined { const storedFunction = actionMap[name]; if (storedFunction === undefined) { return undefined; } const constructor = (storedFunction as unknown) as typeof ActionController; - return new constructor(scheduler); + return new constructor(scheduler, villageStateRepository); } -export function err(msg: string): never { - throw new ActionError(msg); +export function taskError(msg: string): never { + throw new AbortTaskError(msg); } export class ActionController { protected scheduler: Scheduler; - constructor(scheduler: Scheduler) { + protected villageStateRepository: VillageStateRepository; + constructor(scheduler: Scheduler, villageStateRepository: VillageStateRepository) { this.scheduler = scheduler; + this.villageStateRepository = villageStateRepository; } async run(args: Args, task: Task) {} ensureSameVillage(args: Args, task: Task) { - let villageId = args.villageId || err('Undefined village id'); + let villageId = args.villageId || taskError('Undefined village id'); const activeVillageId = grabActiveVillageId(); if (villageId !== activeVillageId) { throw new TryLaterError(aroundMinutes(1), 'Not same village'); diff --git a/src/Action/BalanceHeroResourcesAction.ts b/src/Action/BalanceHeroResourcesAction.ts index 5c1072c..0b13688 100644 --- a/src/Action/BalanceHeroResourcesAction.ts +++ b/src/Action/BalanceHeroResourcesAction.ts @@ -1,15 +1,11 @@ import { ActionController, registerAction } from './ActionController'; -import { grabVillageResources, grabVillageResourceStorage } from '../Page/ResourcesBlock'; import { changeHeroResource, grabCurrentHeroResource } from '../Page/HeroPage'; -import { grabActiveVillageId, grabVillageList } from '../Page/VillageBlock'; +import { grabActiveVillageId } from '../Page/VillageBlock'; import { HeroStorage } from '../Storage/HeroStorage'; import { calcHeroResource } from '../Core/HeroBalance'; import { HeroAllResources } from '../Core/Hero'; import { Args } from '../Queue/Args'; import { Task } from '../Queue/TaskProvider'; -import { Resources } from '../Core/Resources'; -import { createVillageStates } from '../VillageState'; -import { ActionError } from '../Errors'; @registerAction export class BalanceHeroResourcesAction extends ActionController { @@ -22,13 +18,7 @@ export class BalanceHeroResourcesAction extends ActionController { return; } - const villages = grabVillageList(); - const villageStates = createVillageStates(villages, this.scheduler); - const thisVillageState = villageStates.find(s => s.id === thisVillageId); - - if (!thisVillageState) { - throw new ActionError(`State for village ${thisVillageId} not found`); - } + const thisVillageState = this.villageStateRepository.getVillageState(thisVillageId); const requirements = [ thisVillageState.required.balance, diff --git a/src/Action/BuildBuildingAction.ts b/src/Action/BuildBuildingAction.ts index 00ef87e..bde2703 100644 --- a/src/Action/BuildBuildingAction.ts +++ b/src/Action/BuildBuildingAction.ts @@ -1,4 +1,4 @@ -import { ActionController, err, registerAction } from './ActionController'; +import { ActionController, taskError, registerAction } from './ActionController'; import { GrabError, TryLaterError } from '../Errors'; import { clickBuildButton } from '../Page/BuildingPage/BuildingPage'; import { aroundMinutes } from '../utils'; @@ -11,7 +11,7 @@ export class BuildBuildingAction extends ActionController { try { this.ensureSameVillage(args, task); this.ensureBuildingQueueIsEmpty(); - const buildTypeId = args.buildTypeId || err('Undefined build type id'); + const buildTypeId = args.buildTypeId || taskError('Undefined build type id'); clickBuildButton(buildTypeId); } catch (e) { if (e instanceof GrabError) { diff --git a/src/Action/ForgeImprovementAction.ts b/src/Action/ForgeImprovementAction.ts index a6a2bbe..0d8cc87 100644 --- a/src/Action/ForgeImprovementAction.ts +++ b/src/Action/ForgeImprovementAction.ts @@ -1,4 +1,4 @@ -import { ActionController, err, registerAction } from './ActionController'; +import { ActionController, taskError, registerAction } from './ActionController'; import { GrabError, TryLaterError } from '../Errors'; import { aroundMinutes } from '../utils'; import { Args } from '../Queue/Args'; @@ -10,7 +10,7 @@ export class ForgeImprovementAction extends ActionController { async run(args: Args, task: Task): Promise { try { this.ensureSameVillage(args, task); - const unitId = args.unitId || err('No unitId in args'); + const unitId = args.unitId || taskError('No unitId in args'); clickResearchButton(unitId); } catch (e) { if (e instanceof GrabError) { diff --git a/src/Action/SendResourcesAction.ts b/src/Action/SendResourcesAction.ts index 8d54ae8..394ab3f 100644 --- a/src/Action/SendResourcesAction.ts +++ b/src/Action/SendResourcesAction.ts @@ -1,4 +1,4 @@ -import { ActionController, err, registerAction } from './ActionController'; +import { ActionController, taskError, registerAction } from './ActionController'; import { AbortTaskError, TryLaterError } from '../Errors'; import { Resources } from '../Core/Resources'; import { Coordinates, Village } from '../Core/Village'; @@ -15,7 +15,7 @@ const TIMEOUT = 15; @registerAction export class SendResourcesAction extends ActionController { async run(args: Args, task: Task): Promise { - const coordinates = Coordinates.fromObject(args.coordinates || err('No coordinates')); + const coordinates = Coordinates.fromObject(args.coordinates || taskError('No coordinates')); const recipientVillage = args.targetVillageId ? this.findRecipientVillageById(args.targetVillageId) diff --git a/src/Action/TrainTrooperAction.ts b/src/Action/TrainTrooperAction.ts index 2a436a9..4d85f4f 100644 --- a/src/Action/TrainTrooperAction.ts +++ b/src/Action/TrainTrooperAction.ts @@ -1,4 +1,4 @@ -import { ActionController, err, registerAction } from './ActionController'; +import { ActionController, taskError, registerAction } from './ActionController'; import { TryLaterError } from '../Errors'; import { aroundMinutes, randomInRange } from '../utils'; import { Args } from '../Queue/Args'; @@ -10,9 +10,9 @@ import { Resources } from '../Core/Resources'; @registerAction export class TrainTrooperAction extends ActionController { async run(args: Args, task: Task): Promise { - const troopId = args.troopId || err('No troop id'); - const trainCount = args.trainCount || err('No troop train count'); - const troopResources = args.troopResources || err('No troop resources'); + const troopId = args.troopId || taskError('No troop id'); + const trainCount = args.trainCount || taskError('No troop train count'); + const troopResources = args.troopResources || taskError('No troop resources'); const availableCount = getAvailableCount(troopId); const desiredCount = randomInRange(3, 12); diff --git a/src/Container.ts b/src/Container.ts index 197c594..0a4d978 100644 --- a/src/Container.ts +++ b/src/Container.ts @@ -8,6 +8,7 @@ import { DataStorageTaskProvider } from './Queue/DataStorageTaskProvider'; import { Statistics } from './Statistics'; import { StatisticsStorage } from './Storage/StatisticsStorage'; import { VillageRepository, VillageRepositoryInterface } from './VillageRepository'; +import { VillageStateRepository } from './VillageState'; export class Container { private readonly version: string; @@ -16,7 +17,7 @@ export class Container { this.version = version; } - private _villageRepository: VillageRepositoryInterface | undefined; + private _villageRepository: VillageRepository | undefined; get villageRepository(): VillageRepository { this._villageRepository = @@ -27,6 +28,17 @@ export class Container { return this._villageRepository; } + private _statistics: Statistics | undefined; + + get statistics(): Statistics { + this._statistics = + this._statistics || + (() => { + return new Statistics(new StatisticsStorage()); + })(); + return this._statistics; + } + private _scheduler: Scheduler | undefined; get scheduler(): Scheduler { @@ -41,13 +53,24 @@ export class Container { return this._scheduler; } + private _villageStateRepository: VillageStateRepository | undefined; + + get villageStateRepository(): VillageStateRepository { + this._villageStateRepository = + this._villageStateRepository || + (() => { + return new VillageStateRepository(this.villageRepository, this.scheduler); + })(); + return this._villageStateRepository; + } + private _executor: Executor | undefined; get executor(): Executor { this._executor = this._executor || (() => { - return new Executor(this.version, this.scheduler, this.statistics); + return new Executor(this.version, this.scheduler, this.villageStateRepository, this.statistics); })(); return this._executor; } @@ -58,19 +81,8 @@ export class Container { this._controlPanel = this._controlPanel || (() => { - return new ControlPanel(this.version, this.scheduler); + return new ControlPanel(this.version, this.scheduler, this.villageStateRepository); })(); return this._controlPanel; } - - private _statistics: Statistics | undefined; - - get statistics(): Statistics { - this._statistics = - this._statistics || - (() => { - return new Statistics(new StatisticsStorage()); - })(); - return this._statistics; - } } diff --git a/src/ControlPanel.ts b/src/ControlPanel.ts index 171c489..aa54edb 100644 --- a/src/ControlPanel.ts +++ b/src/ControlPanel.ts @@ -2,7 +2,7 @@ import { notify, parseLocation, timestamp, uniqId, waitForLoad } from './utils'; import { Scheduler } from './Scheduler'; import { BuildingPageController } from './Page/BuildingPageController'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; -import { grabActiveVillageId, grabVillageList } from './Page/VillageBlock'; +import { grabActiveVillageId } from './Page/VillageBlock'; import { grabResourceDeposits, onBuildingSlotCtrlClick, @@ -17,7 +17,7 @@ import { ConsoleLogger, Logger } from './Logger'; import { DataStorage } from './DataStorage'; import { getBuildingPageAttributes, isBuildingPage } from './Page/PageDetectors'; import { ExecutionStorage } from './Storage/ExecutionStorage'; -import { createVillageStates, VillageState } from './VillageState'; +import { VillageState, VillageStateRepository } from './VillageState'; import { Task } from './Queue/TaskProvider'; import { Action } from './Queue/ActionQueue'; @@ -47,11 +47,13 @@ interface GameState { export class ControlPanel { private readonly version: string; private readonly scheduler: Scheduler; + private readonly villageStateRepository: VillageStateRepository; private readonly logger: Logger; - constructor(version: string, scheduler: Scheduler) { + constructor(version: string, scheduler: Scheduler, villageStateRepository: VillageStateRepository) { this.version = version; this.scheduler = scheduler; + this.villageStateRepository = villageStateRepository; this.logger = new ConsoleLogger(this.constructor.name); } @@ -64,6 +66,7 @@ export class ControlPanel { const villageId = grabActiveVillageId(); const scheduler = this.scheduler; + const villageStateRepository = this.villageStateRepository; const executionState = new ExecutionStorage(); @@ -91,7 +94,7 @@ export class ControlPanel { }, refreshVillages() { - this.villageStates = createVillageStates(grabVillageList(), scheduler); + this.villageStates = villageStateRepository.getAllVillageStates(); for (let state of this.villageStates) { if (state.village.active) { this.activeVillageState = state; diff --git a/src/Errors.ts b/src/Errors.ts index 7ef68f8..c28e26a 100644 --- a/src/Errors.ts +++ b/src/Errors.ts @@ -5,6 +5,13 @@ export class GrabError extends Error { } } +export class VillageNotFound extends Error { + constructor(msg: string = '') { + super(msg); + Object.setPrototypeOf(this, VillageNotFound.prototype); + } +} + export class ActionError extends Error { constructor(msg: string = '') { super(msg); diff --git a/src/Executor.ts b/src/Executor.ts index 0368789..385547b 100644 --- a/src/Executor.ts +++ b/src/Executor.ts @@ -1,5 +1,5 @@ import { markPage, sleepMicro, timestamp, waitForLoad } from './utils'; -import { AbortTaskError, ActionError, GrabError, TryLaterError } from './Errors'; +import { AbortTaskError, ActionError, GrabError, TryLaterError, VillageNotFound } from './Errors'; import { TaskQueueRenderer } from './TaskQueueRenderer'; import { createActionHandler } from './Action/ActionController'; import { ConsoleLogger, Logger } from './Logger'; @@ -10,6 +10,7 @@ import { ExecutionStorage } from './Storage/ExecutionStorage'; import { Action } from './Queue/ActionQueue'; import { Task } from './Queue/TaskProvider'; import { createTaskHandler } from './Task/TaskMap'; +import { VillageStateRepository } from './VillageState'; export interface ExecutionSettings { pauseTs: number; @@ -18,14 +19,21 @@ export interface ExecutionSettings { export class Executor { private readonly version: string; private readonly scheduler: Scheduler; + private readonly villageStateRepository: VillageStateRepository; private grabbers: GrabberManager; private statistics: Statistics; private executionState: ExecutionStorage; private logger: Logger; - constructor(version: string, scheduler: Scheduler, statistics: Statistics) { + constructor( + version: string, + scheduler: Scheduler, + villageStateRepository: VillageStateRepository, + statistics: Statistics + ) { this.version = version; this.scheduler = scheduler; + this.villageStateRepository = villageStateRepository; this.grabbers = new GrabberManager(scheduler); this.statistics = statistics; this.executionState = new ExecutionStorage(); @@ -97,7 +105,7 @@ export class Executor { } private async processActionCommand(cmd: Action, task: Task) { - const actionHandler = createActionHandler(cmd.name, this.scheduler); + const actionHandler = createActionHandler(cmd.name, this.scheduler, this.villageStateRepository); this.logger.info('PROCESS ACTION', cmd.name, actionHandler); if (cmd.args.taskId !== task.id) { throw new ActionError(`Action task id ${cmd.args.taskId} not equal current task id ${task.id}`); @@ -125,24 +133,30 @@ export class Executor { this.scheduler.clearActions(); if (err instanceof AbortTaskError) { - this.logger.warn('ABORT TASK', task.id, 'MSG', err.message); + this.logger.warn('Abort task', task.id, 'MSG', err.message); + this.scheduler.removeTask(task.id); + return; + } + + if (err instanceof VillageNotFound) { + this.logger.error('Village not found, abort task', task.id, 'msg', err.message); this.scheduler.removeTask(task.id); return; } if (err instanceof TryLaterError) { - this.logger.warn('TRY', task.id, 'AFTER', err.seconds, 'MSG', err.message); + this.logger.warn('Try', task.id, 'after', err.seconds, 'msg', err.message); this.scheduler.postponeTask(task.id, err.seconds); return; } - if (err instanceof ActionError) { - this.logger.error('ACTION ABORTED', err.message); + if (err instanceof GrabError) { + this.logger.error('Layout element not found, abort action', err.message); return; } - if (err instanceof GrabError) { - this.logger.error('ELEMENT NOT FOUND, ACTION ABORTED', err.message); + if (err instanceof ActionError) { + this.logger.error('Abort action', err.message); return; } diff --git a/src/VillageState.ts b/src/VillageState.ts index e99b65f..32f18d7 100644 --- a/src/VillageState.ts +++ b/src/VillageState.ts @@ -3,6 +3,8 @@ import { Scheduler } from './Scheduler'; import { Resources } from './Core/Resources'; import { VillageStorage } from './Storage/VillageStorage'; import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings'; +import { VillageRepositoryInterface } from './VillageRepository'; +import { VillageNotFound } from './Errors'; interface StorageBalance { resources: Resources; @@ -127,7 +129,30 @@ function createVillageState( return { ...state, commitments, shipment: villageIds }; } -export function createVillageStates(villages: Array, scheduler: Scheduler): Array { +function getVillageStates(villages: Array, scheduler: Scheduler): Array { const ownStates = createVillageOwnStates(villages, scheduler); return villages.map(village => createVillageState(ownStates[village.id], ownStates, scheduler)); } + +export class VillageStateRepository { + private villageRepository: VillageRepositoryInterface; + private scheduler: Scheduler; + + constructor(villageRepository: VillageRepositoryInterface, scheduler: Scheduler) { + this.villageRepository = villageRepository; + this.scheduler = scheduler; + } + + getAllVillageStates(): Array { + return getVillageStates(this.villageRepository.all(), this.scheduler); + } + + getVillageState(villageId: number): VillageState { + const states = getVillageStates(this.villageRepository.all(), this.scheduler); + const needle = states.find(s => s.id === villageId); + if (!needle) { + throw new VillageNotFound(`Village ${villageId} not found`); + } + return needle; + } +}