Improve hero balance action

This commit is contained in:
Anton Vakhrushev 2020-05-10 10:12:01 +03:00
parent 18f43c3931
commit d1a2128411
11 changed files with 115 additions and 57 deletions

View File

@ -1,10 +1,11 @@
import { Scheduler } from '../Scheduler'; import { Scheduler } from '../Scheduler';
import { ActionError, TryLaterError } from '../Errors'; import { AbortTaskError, TryLaterError } from '../Errors';
import { grabActiveVillageId } from '../Page/VillageBlock'; import { grabActiveVillageId } from '../Page/VillageBlock';
import { aroundMinutes } from '../utils'; import { aroundMinutes } from '../utils';
import { Args } from '../Queue/Args'; import { Args } from '../Queue/Args';
import { Task } from '../Queue/TaskProvider'; import { Task } from '../Queue/TaskProvider';
import { VillageStorage } from '../Storage/VillageStorage'; import { VillageStorage } from '../Storage/VillageStorage';
import { VillageStateRepository } from '../VillageState';
const actionMap: { [name: string]: Function | undefined } = {}; const actionMap: { [name: string]: Function | undefined } = {};
@ -12,29 +13,35 @@ export function registerAction(constructor: Function) {
actionMap[constructor.name] = constructor; 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]; const storedFunction = actionMap[name];
if (storedFunction === undefined) { if (storedFunction === undefined) {
return undefined; return undefined;
} }
const constructor = (storedFunction as unknown) as typeof ActionController; const constructor = (storedFunction as unknown) as typeof ActionController;
return new constructor(scheduler); return new constructor(scheduler, villageStateRepository);
} }
export function err(msg: string): never { export function taskError(msg: string): never {
throw new ActionError(msg); throw new AbortTaskError(msg);
} }
export class ActionController { export class ActionController {
protected scheduler: Scheduler; protected scheduler: Scheduler;
constructor(scheduler: Scheduler) { protected villageStateRepository: VillageStateRepository;
constructor(scheduler: Scheduler, villageStateRepository: VillageStateRepository) {
this.scheduler = scheduler; this.scheduler = scheduler;
this.villageStateRepository = villageStateRepository;
} }
async run(args: Args, task: Task) {} async run(args: Args, task: Task) {}
ensureSameVillage(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(); const activeVillageId = grabActiveVillageId();
if (villageId !== activeVillageId) { if (villageId !== activeVillageId) {
throw new TryLaterError(aroundMinutes(1), 'Not same village'); throw new TryLaterError(aroundMinutes(1), 'Not same village');

View File

@ -1,15 +1,11 @@
import { ActionController, registerAction } from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { grabVillageResources, grabVillageResourceStorage } from '../Page/ResourcesBlock';
import { changeHeroResource, grabCurrentHeroResource } from '../Page/HeroPage'; import { changeHeroResource, grabCurrentHeroResource } from '../Page/HeroPage';
import { grabActiveVillageId, grabVillageList } from '../Page/VillageBlock'; import { grabActiveVillageId } from '../Page/VillageBlock';
import { HeroStorage } from '../Storage/HeroStorage'; import { HeroStorage } from '../Storage/HeroStorage';
import { calcHeroResource } from '../Core/HeroBalance'; import { calcHeroResource } from '../Core/HeroBalance';
import { HeroAllResources } from '../Core/Hero'; import { HeroAllResources } from '../Core/Hero';
import { Args } from '../Queue/Args'; import { Args } from '../Queue/Args';
import { Task } from '../Queue/TaskProvider'; import { Task } from '../Queue/TaskProvider';
import { Resources } from '../Core/Resources';
import { createVillageStates } from '../VillageState';
import { ActionError } from '../Errors';
@registerAction @registerAction
export class BalanceHeroResourcesAction extends ActionController { export class BalanceHeroResourcesAction extends ActionController {
@ -22,13 +18,7 @@ export class BalanceHeroResourcesAction extends ActionController {
return; return;
} }
const villages = grabVillageList(); const thisVillageState = this.villageStateRepository.getVillageState(thisVillageId);
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 requirements = [ const requirements = [
thisVillageState.required.balance, thisVillageState.required.balance,

View File

@ -1,4 +1,4 @@
import { ActionController, err, registerAction } from './ActionController'; import { ActionController, taskError, registerAction } from './ActionController';
import { GrabError, TryLaterError } from '../Errors'; import { GrabError, TryLaterError } from '../Errors';
import { clickBuildButton } from '../Page/BuildingPage/BuildingPage'; import { clickBuildButton } from '../Page/BuildingPage/BuildingPage';
import { aroundMinutes } from '../utils'; import { aroundMinutes } from '../utils';
@ -11,7 +11,7 @@ export class BuildBuildingAction extends ActionController {
try { try {
this.ensureSameVillage(args, task); this.ensureSameVillage(args, task);
this.ensureBuildingQueueIsEmpty(); this.ensureBuildingQueueIsEmpty();
const buildTypeId = args.buildTypeId || err('Undefined build type id'); const buildTypeId = args.buildTypeId || taskError('Undefined build type id');
clickBuildButton(buildTypeId); clickBuildButton(buildTypeId);
} catch (e) { } catch (e) {
if (e instanceof GrabError) { if (e instanceof GrabError) {

View File

@ -1,4 +1,4 @@
import { ActionController, err, registerAction } from './ActionController'; import { ActionController, taskError, registerAction } from './ActionController';
import { GrabError, TryLaterError } from '../Errors'; import { GrabError, TryLaterError } from '../Errors';
import { aroundMinutes } from '../utils'; import { aroundMinutes } from '../utils';
import { Args } from '../Queue/Args'; import { Args } from '../Queue/Args';
@ -10,7 +10,7 @@ export class ForgeImprovementAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
try { try {
this.ensureSameVillage(args, task); this.ensureSameVillage(args, task);
const unitId = args.unitId || err('No unitId in args'); const unitId = args.unitId || taskError('No unitId in args');
clickResearchButton(unitId); clickResearchButton(unitId);
} catch (e) { } catch (e) {
if (e instanceof GrabError) { if (e instanceof GrabError) {

View File

@ -1,4 +1,4 @@
import { ActionController, err, registerAction } from './ActionController'; import { ActionController, taskError, registerAction } from './ActionController';
import { AbortTaskError, TryLaterError } from '../Errors'; import { AbortTaskError, TryLaterError } from '../Errors';
import { Resources } from '../Core/Resources'; import { Resources } from '../Core/Resources';
import { Coordinates, Village } from '../Core/Village'; import { Coordinates, Village } from '../Core/Village';
@ -15,7 +15,7 @@ const TIMEOUT = 15;
@registerAction @registerAction
export class SendResourcesAction extends ActionController { export class SendResourcesAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { 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 const recipientVillage = args.targetVillageId
? this.findRecipientVillageById(args.targetVillageId) ? this.findRecipientVillageById(args.targetVillageId)

View File

@ -1,4 +1,4 @@
import { ActionController, err, registerAction } from './ActionController'; import { ActionController, taskError, registerAction } from './ActionController';
import { TryLaterError } from '../Errors'; import { TryLaterError } from '../Errors';
import { aroundMinutes, randomInRange } from '../utils'; import { aroundMinutes, randomInRange } from '../utils';
import { Args } from '../Queue/Args'; import { Args } from '../Queue/Args';
@ -10,9 +10,9 @@ import { Resources } from '../Core/Resources';
@registerAction @registerAction
export class TrainTrooperAction extends ActionController { export class TrainTrooperAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const troopId = args.troopId || err('No troop id'); const troopId = args.troopId || taskError('No troop id');
const trainCount = args.trainCount || err('No troop train count'); const trainCount = args.trainCount || taskError('No troop train count');
const troopResources = args.troopResources || err('No troop resources'); const troopResources = args.troopResources || taskError('No troop resources');
const availableCount = getAvailableCount(troopId); const availableCount = getAvailableCount(troopId);
const desiredCount = randomInRange(3, 12); const desiredCount = randomInRange(3, 12);

View File

@ -8,6 +8,7 @@ import { DataStorageTaskProvider } from './Queue/DataStorageTaskProvider';
import { Statistics } from './Statistics'; import { Statistics } from './Statistics';
import { StatisticsStorage } from './Storage/StatisticsStorage'; import { StatisticsStorage } from './Storage/StatisticsStorage';
import { VillageRepository, VillageRepositoryInterface } from './VillageRepository'; import { VillageRepository, VillageRepositoryInterface } from './VillageRepository';
import { VillageStateRepository } from './VillageState';
export class Container { export class Container {
private readonly version: string; private readonly version: string;
@ -16,7 +17,7 @@ export class Container {
this.version = version; this.version = version;
} }
private _villageRepository: VillageRepositoryInterface | undefined; private _villageRepository: VillageRepository | undefined;
get villageRepository(): VillageRepository { get villageRepository(): VillageRepository {
this._villageRepository = this._villageRepository =
@ -27,6 +28,17 @@ export class Container {
return this._villageRepository; 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; private _scheduler: Scheduler | undefined;
get scheduler(): Scheduler { get scheduler(): Scheduler {
@ -41,13 +53,24 @@ export class Container {
return this._scheduler; 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; private _executor: Executor | undefined;
get executor(): Executor { get executor(): Executor {
this._executor = this._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; return this._executor;
} }
@ -58,19 +81,8 @@ export class Container {
this._controlPanel = this._controlPanel =
this._controlPanel || this._controlPanel ||
(() => { (() => {
return new ControlPanel(this.version, this.scheduler); return new ControlPanel(this.version, this.scheduler, this.villageStateRepository);
})(); })();
return this._controlPanel; return this._controlPanel;
} }
private _statistics: Statistics | undefined;
get statistics(): Statistics {
this._statistics =
this._statistics ||
(() => {
return new Statistics(new StatisticsStorage());
})();
return this._statistics;
}
} }

View File

@ -2,7 +2,7 @@ import { notify, parseLocation, timestamp, uniqId, waitForLoad } from './utils';
import { Scheduler } from './Scheduler'; import { Scheduler } from './Scheduler';
import { BuildingPageController } from './Page/BuildingPageController'; import { BuildingPageController } from './Page/BuildingPageController';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import { grabActiveVillageId, grabVillageList } from './Page/VillageBlock'; import { grabActiveVillageId } from './Page/VillageBlock';
import { import {
grabResourceDeposits, grabResourceDeposits,
onBuildingSlotCtrlClick, onBuildingSlotCtrlClick,
@ -17,7 +17,7 @@ import { ConsoleLogger, Logger } from './Logger';
import { DataStorage } from './DataStorage'; import { DataStorage } from './DataStorage';
import { getBuildingPageAttributes, isBuildingPage } from './Page/PageDetectors'; import { getBuildingPageAttributes, isBuildingPage } from './Page/PageDetectors';
import { ExecutionStorage } from './Storage/ExecutionStorage'; import { ExecutionStorage } from './Storage/ExecutionStorage';
import { createVillageStates, VillageState } from './VillageState'; import { VillageState, VillageStateRepository } from './VillageState';
import { Task } from './Queue/TaskProvider'; import { Task } from './Queue/TaskProvider';
import { Action } from './Queue/ActionQueue'; import { Action } from './Queue/ActionQueue';
@ -47,11 +47,13 @@ interface GameState {
export class ControlPanel { export class ControlPanel {
private readonly version: string; private readonly version: string;
private readonly scheduler: Scheduler; private readonly scheduler: Scheduler;
private readonly villageStateRepository: VillageStateRepository;
private readonly logger: Logger; private readonly logger: Logger;
constructor(version: string, scheduler: Scheduler) { constructor(version: string, scheduler: Scheduler, villageStateRepository: VillageStateRepository) {
this.version = version; this.version = version;
this.scheduler = scheduler; this.scheduler = scheduler;
this.villageStateRepository = villageStateRepository;
this.logger = new ConsoleLogger(this.constructor.name); this.logger = new ConsoleLogger(this.constructor.name);
} }
@ -64,6 +66,7 @@ export class ControlPanel {
const villageId = grabActiveVillageId(); const villageId = grabActiveVillageId();
const scheduler = this.scheduler; const scheduler = this.scheduler;
const villageStateRepository = this.villageStateRepository;
const executionState = new ExecutionStorage(); const executionState = new ExecutionStorage();
@ -91,7 +94,7 @@ export class ControlPanel {
}, },
refreshVillages() { refreshVillages() {
this.villageStates = createVillageStates(grabVillageList(), scheduler); this.villageStates = villageStateRepository.getAllVillageStates();
for (let state of this.villageStates) { for (let state of this.villageStates) {
if (state.village.active) { if (state.village.active) {
this.activeVillageState = state; this.activeVillageState = state;

View File

@ -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 { export class ActionError extends Error {
constructor(msg: string = '') { constructor(msg: string = '') {
super(msg); super(msg);

View File

@ -1,5 +1,5 @@
import { markPage, sleepMicro, timestamp, waitForLoad } from './utils'; 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 { TaskQueueRenderer } from './TaskQueueRenderer';
import { createActionHandler } from './Action/ActionController'; import { createActionHandler } from './Action/ActionController';
import { ConsoleLogger, Logger } from './Logger'; import { ConsoleLogger, Logger } from './Logger';
@ -10,6 +10,7 @@ import { ExecutionStorage } from './Storage/ExecutionStorage';
import { Action } from './Queue/ActionQueue'; import { Action } from './Queue/ActionQueue';
import { Task } from './Queue/TaskProvider'; import { Task } from './Queue/TaskProvider';
import { createTaskHandler } from './Task/TaskMap'; import { createTaskHandler } from './Task/TaskMap';
import { VillageStateRepository } from './VillageState';
export interface ExecutionSettings { export interface ExecutionSettings {
pauseTs: number; pauseTs: number;
@ -18,14 +19,21 @@ export interface ExecutionSettings {
export class Executor { export class Executor {
private readonly version: string; private readonly version: string;
private readonly scheduler: Scheduler; private readonly scheduler: Scheduler;
private readonly villageStateRepository: VillageStateRepository;
private grabbers: GrabberManager; private grabbers: GrabberManager;
private statistics: Statistics; private statistics: Statistics;
private executionState: ExecutionStorage; private executionState: ExecutionStorage;
private logger: Logger; private logger: Logger;
constructor(version: string, scheduler: Scheduler, statistics: Statistics) { constructor(
version: string,
scheduler: Scheduler,
villageStateRepository: VillageStateRepository,
statistics: Statistics
) {
this.version = version; this.version = version;
this.scheduler = scheduler; this.scheduler = scheduler;
this.villageStateRepository = villageStateRepository;
this.grabbers = new GrabberManager(scheduler); this.grabbers = new GrabberManager(scheduler);
this.statistics = statistics; this.statistics = statistics;
this.executionState = new ExecutionStorage(); this.executionState = new ExecutionStorage();
@ -97,7 +105,7 @@ export class Executor {
} }
private async processActionCommand(cmd: Action, task: Task) { 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); this.logger.info('PROCESS ACTION', cmd.name, actionHandler);
if (cmd.args.taskId !== task.id) { if (cmd.args.taskId !== task.id) {
throw new ActionError(`Action task id ${cmd.args.taskId} not equal current task id ${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(); this.scheduler.clearActions();
if (err instanceof AbortTaskError) { 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); this.scheduler.removeTask(task.id);
return; return;
} }
if (err instanceof TryLaterError) { 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); this.scheduler.postponeTask(task.id, err.seconds);
return; return;
} }
if (err instanceof ActionError) { if (err instanceof GrabError) {
this.logger.error('ACTION ABORTED', err.message); this.logger.error('Layout element not found, abort action', err.message);
return; return;
} }
if (err instanceof GrabError) { if (err instanceof ActionError) {
this.logger.error('ELEMENT NOT FOUND, ACTION ABORTED', err.message); this.logger.error('Abort action', err.message);
return; return;
} }

View File

@ -3,6 +3,8 @@ import { Scheduler } from './Scheduler';
import { Resources } from './Core/Resources'; import { Resources } from './Core/Resources';
import { VillageStorage } from './Storage/VillageStorage'; import { VillageStorage } from './Storage/VillageStorage';
import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings'; import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings';
import { VillageRepositoryInterface } from './VillageRepository';
import { VillageNotFound } from './Errors';
interface StorageBalance { interface StorageBalance {
resources: Resources; resources: Resources;
@ -127,7 +129,30 @@ function createVillageState(
return { ...state, commitments, shipment: villageIds }; 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); const ownStates = createVillageOwnStates(villages, scheduler);
return villages.map(village => createVillageState(ownStates[village.id], ownStates, 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;
}
}