Improve hero balance action
This commit is contained in:
		| @@ -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'); | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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<any> { | ||||
|         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) { | ||||
|   | ||||
| @@ -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<any> { | ||||
|         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) | ||||
|   | ||||
| @@ -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<any> { | ||||
|         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); | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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<Village>, scheduler: Scheduler): Array<VillageState> { | ||||
| function getVillageStates(villages: Array<Village>, scheduler: Scheduler): Array<VillageState> { | ||||
|     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<VillageState> { | ||||
|         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; | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user