From faed76df4d83a0c36e0f2f33d389c57c6d8530e6 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sun, 24 May 2020 21:05:29 +0300 Subject: [PATCH] Add smart resource transfer --- src/Action/FindSendResourcesPath.ts | 46 +++++++++ src/Action/SendResourcesAction.ts | 124 ++++--------------------- src/Core/Market.ts | 5 + src/Executor.ts | 7 +- src/Grabber/MarketPageGrabber.ts | 3 +- src/Page/BuildingPage/MarketPage.ts | 8 +- src/ResourceTransfer.ts | 49 ++++++++++ src/Scheduler.ts | 18 ++-- src/Statistics.ts | 10 +- src/Storage/ResourceTransferStorage.ts | 33 +++++++ src/Storage/VillageStorage.ts | 13 ++- src/Task/SendResourcesTask.ts | 24 ++--- src/Task/TaskController.ts | 7 +- src/Task/TaskMap.ts | 9 +- src/VillageController.ts | 47 +++++++++- src/VillageFactory.ts | 9 ++ src/utils.ts | 8 +- 17 files changed, 272 insertions(+), 148 deletions(-) create mode 100644 src/Action/FindSendResourcesPath.ts create mode 100644 src/ResourceTransfer.ts create mode 100644 src/Storage/ResourceTransferStorage.ts diff --git a/src/Action/FindSendResourcesPath.ts b/src/Action/FindSendResourcesPath.ts new file mode 100644 index 0000000..6428437 --- /dev/null +++ b/src/Action/FindSendResourcesPath.ts @@ -0,0 +1,46 @@ +import { ActionController, registerAction } from './ActionController'; +import { FailTaskError, taskError, TryLaterError } from '../Errors'; +import { Resources } from '../Core/Resources'; +import { Coordinates } from '../Core/Village'; +import { aroundMinutes, timestamp } from '../utils'; +import { Args } from '../Queue/Args'; +import { Task } from '../Queue/TaskProvider'; +import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/MarketPage'; +import { VillageState } from '../VillageState'; +import { MerchantsInfo } from '../Core/Market'; +import { goToMarketSendResourcesPage, goToResourceViewPage } from '../Task/ActionBundles'; +import { ResourceTransferCalculator, ResourceTransferReport } from '../ResourceTransfer'; +import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage'; +import { path } from '../Helpers/Path'; +import { MARKET_ID } from '../Core/Buildings'; + +@registerAction +export class FindSendResourcesPath extends ActionController { + async run(args: Args, task: Task): Promise { + const reports: Array = []; + const calculator = new ResourceTransferCalculator(this.villageFactory); + + const villages = this.villageFactory.getAllVillages(); + for (let fromVillage of villages) { + for (let toVillage of villages) { + reports.push(calculator.calc(fromVillage.id, toVillage.id)); + } + } + + reports.sort((r1, r2) => r2.score - r1.score); + + const bestReport = reports.shift(); + + if (!bestReport) { + throw new FailTaskError('No best report for transfer resources'); + } + + console.log('Best report', bestReport); + + const storage = new ResourceTransferStorage(); + storage.storeReport(bestReport); + + const marketPath = path('/build.php', { newdid: bestReport.fromVillageId, gid: MARKET_ID, t: 5 }); + window.location.assign(marketPath); + } +} diff --git a/src/Action/SendResourcesAction.ts b/src/Action/SendResourcesAction.ts index e782de5..46e85c1 100644 --- a/src/Action/SendResourcesAction.ts +++ b/src/Action/SendResourcesAction.ts @@ -1,123 +1,33 @@ import { ActionController, registerAction } from './ActionController'; -import { taskError, TryLaterError } from '../Errors'; -import { Resources } from '../Core/Resources'; -import { Coordinates } from '../Core/Village'; -import { aroundMinutes, timestamp } from '../utils'; import { Args } from '../Queue/Args'; import { Task } from '../Queue/TaskProvider'; -import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage'; -import { VillageState } from '../VillageState'; +import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/MarketPage'; +import { ResourceTransferCalculator } from '../ResourceTransfer'; +import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage'; +import { Resources } from '../Core/Resources'; +import { AbortTaskError } from '../Errors'; @registerAction export class SendResourcesAction extends ActionController { async run(args: Args, task: Task): Promise { - this.ensureSameVillage(args, task); + const storage = new ResourceTransferStorage(); + const savedReport = storage.getReport(); - const senderVillageId = args.villageId || taskError('No source village id'); - const targetVillageId = args.targetVillageId || taskError('No target village id'); + const fromVillage = this.villageFactory.getVillage(savedReport.fromVillageId); + const toVillage = this.villageFactory.getVillage(savedReport.toVillageId); - const coordinates = Coordinates.fromObject(args.coordinates || taskError('No coordinates')); + const coordinates = toVillage.crd; - const senderVillage = this.villageFactory.createState(senderVillageId); - const recipientVillage = this.villageFactory.createState(targetVillageId); + const calculator = new ResourceTransferCalculator(this.villageFactory); + const report = calculator.calc(fromVillage.id, toVillage.id); - const readyToTransfer = this.getResourcesForTransfer(senderVillage, recipientVillage); + console.log('To transfer report', report); - console.log('To transfer res', readyToTransfer); + if (Resources.fromObject(report.resources).empty()) { + throw new AbortTaskError('No resources to transfer'); + } - // Schedule recurrent task - const timeout = senderVillage.settings.sendResourcesTimeout; - this.scheduler.scheduleTask(task.name, task.args, timestamp() + aroundMinutes(timeout)); - - fillSendResourcesForm(readyToTransfer, coordinates); + fillSendResourcesForm(report.resources, coordinates); clickSendButton(); } - - private getMerchantsCapacity(timeout: number): number { - const merchants = grabMerchantsInfo(); - const capacity = merchants.available * merchants.carry; - if (!capacity) { - throw new TryLaterError(aroundMinutes(timeout), 'No merchants'); - } - return capacity; - } - - private getSenderAvailableResources(senderState: VillageState): Resources { - const balance = senderState.required.balance; - const free = balance.max(Resources.zero()); - - console.table([ - { name: 'Sender balance', ...balance }, - { name: 'Sender free', ...free }, - ]); - - const amount = free.amount(); - const threshold = senderState.settings.sendResourcesThreshold; - const timeout = senderState.settings.sendResourcesTimeout; - - if (amount < threshold) { - throw new TryLaterError( - aroundMinutes(timeout), - `No free resources (amount ${amount} < threshold ${threshold})` - ); - } - - return free; - } - - private getRecipientRequirements(recipientState: VillageState, timeout: number): Resources { - const maxPossibleToStore = recipientState.storage.capacity.sub(recipientState.performance); - const currentResources = recipientState.resources; - const incomingResources = recipientState.incomingResources; - const requirementResources = recipientState.required.resources; - const missingResources = requirementResources - .min(maxPossibleToStore) - .sub(incomingResources) - .sub(currentResources) - .max(Resources.zero()); - - console.table([ - { name: 'Recipient max possible', ...maxPossibleToStore }, - { name: 'Recipient resources', ...currentResources }, - { name: 'Recipient incoming', ...incomingResources }, - { name: 'Recipient requirements', ...requirementResources }, - { name: 'Recipient missing', ...missingResources }, - ]); - - if (missingResources.empty()) { - throw new TryLaterError(aroundMinutes(timeout), 'No missing resources'); - } - - return missingResources; - } - - private getResourcesForTransfer(senderState: VillageState, recipientState: VillageState): Resources { - const multiplier = senderState.settings.sendResourcesMultiplier; - const timeout = senderState.settings.sendResourcesTimeout; - const senderReadyResources = this.getSenderAvailableResources(senderState).downTo(multiplier); - const recipientNeedResources = this.getRecipientRequirements(recipientState, timeout).upTo(multiplier); - const contractResources = senderReadyResources.min(recipientNeedResources); - const merchantsCapacity = this.getMerchantsCapacity(timeout); - - let readyToTransfer = contractResources; - if (contractResources.amount() > merchantsCapacity) { - const merchantScale = merchantsCapacity / contractResources.amount(); - readyToTransfer = contractResources.scale(merchantScale).downTo(multiplier); - } - - if (readyToTransfer.empty()) { - throw new TryLaterError(aroundMinutes(timeout), 'Not enough resources to transfer'); - } - - console.log('Merchants capacity', merchantsCapacity); - - console.table([ - { name: 'Sender', ...senderReadyResources }, - { name: 'Recipient', ...recipientNeedResources }, - { name: 'Prepared', ...contractResources }, - { name: 'Ready to transfer', ...readyToTransfer }, - ]); - - return readyToTransfer; - } } diff --git a/src/Core/Market.ts b/src/Core/Market.ts index bf80868..174e6a1 100644 --- a/src/Core/Market.ts +++ b/src/Core/Market.ts @@ -8,3 +8,8 @@ export class IncomingMerchant { this.ts = ts; } } + +export interface MerchantsInfo { + available: number; + carry: number; +} diff --git a/src/Executor.ts b/src/Executor.ts index a4fbf5b..a9a0623 100644 --- a/src/Executor.ts +++ b/src/Executor.ts @@ -10,7 +10,6 @@ import { ExecutionStorage } from './Storage/ExecutionStorage'; import { Action } from './Queue/ActionQueue'; import { Task } from './Queue/TaskProvider'; import { createTaskHandler } from './Task/TaskMap'; -import { VillageStateFactory } from './VillageState'; import { VillageFactory } from './VillageFactory'; export interface ExecutionSettings { @@ -79,8 +78,6 @@ export class Executor { } private async doTaskProcessingStep() { - this.runGrabbers(); - const currentTs = timestamp(); const { task, action } = this.scheduler.nextTask(currentTs); @@ -92,6 +89,8 @@ export class Executor { this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', action); + this.runGrabbers(); + try { if (task && action) { return await this.processActionCommand(action, task); @@ -117,7 +116,7 @@ export class Executor { } private async processTaskCommand(task: Task) { - const taskHandler = createTaskHandler(task.name, this.scheduler); + const taskHandler = createTaskHandler(task.name, this.scheduler, this.villageFactory); this.logger.info('Process task', task.name, task, taskHandler); if (taskHandler) { await taskHandler.run(task); diff --git a/src/Grabber/MarketPageGrabber.ts b/src/Grabber/MarketPageGrabber.ts index 7d000f9..b2de2ed 100644 --- a/src/Grabber/MarketPageGrabber.ts +++ b/src/Grabber/MarketPageGrabber.ts @@ -1,6 +1,6 @@ import { Grabber } from './Grabber'; import { isMarketSendResourcesPage } from '../Page/PageDetectors'; -import { grabIncomingMerchants } from '../Page/BuildingPage/MarketPage'; +import { grabIncomingMerchants, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage'; export class MarketPageGrabber extends Grabber { grab(): void { @@ -9,5 +9,6 @@ export class MarketPageGrabber extends Grabber { } this.storage.storeIncomingMerchants(grabIncomingMerchants()); + this.storage.storeMerchantsInfo(grabMerchantsInfo()); } } diff --git a/src/Page/BuildingPage/MarketPage.ts b/src/Page/BuildingPage/MarketPage.ts index a205794..58c569e 100644 --- a/src/Page/BuildingPage/MarketPage.ts +++ b/src/Page/BuildingPage/MarketPage.ts @@ -1,7 +1,7 @@ import { getNumber, uniqId } from '../../utils'; -import { Resources } from '../../Core/Resources'; +import { Resources, ResourcesInterface } from '../../Core/Resources'; import { Coordinates } from '../../Core/Village'; -import { IncomingMerchant } from '../../Core/Market'; +import { IncomingMerchant, MerchantsInfo } from '../../Core/Market'; import { grabResourcesFromList } from './BuildingPage'; interface SendResourcesClickHandler { @@ -31,13 +31,13 @@ export function createSendResourcesButton(onClickHandler: SendResourcesClickHand jQuery(`#${id}`).on('click', createHandler()); } -export function grabMerchantsInfo() { +export function grabMerchantsInfo(): MerchantsInfo { const available = getNumber(jQuery('.merchantsAvailable').text()); const carry = getNumber(jQuery('.carry b').text()); return { available, carry }; } -export function fillSendResourcesForm(resources: Resources, crd: Coordinates) { +export function fillSendResourcesForm(resources: ResourcesInterface, crd: Coordinates) { const sendSelect = jQuery('#send_select'); sendSelect.find('#r1').val(resources.lumber); sendSelect.find('#r2').val(resources.clay); diff --git a/src/ResourceTransfer.ts b/src/ResourceTransfer.ts new file mode 100644 index 0000000..1c7e3f6 --- /dev/null +++ b/src/ResourceTransfer.ts @@ -0,0 +1,49 @@ +import { VillageFactory } from './VillageFactory'; +import { ResourcesInterface } from './Core/Resources'; + +export interface ResourceTransferReport { + fromVillageId: number; + toVillageId: number; + resources: ResourcesInterface; + score: number; +} + +export class ResourceTransferCalculator { + private factory: VillageFactory; + constructor(factory: VillageFactory) { + this.factory = factory; + } + + calc(fromVillageId: number, toVillageId: number): ResourceTransferReport { + const senderState = this.factory.createState(fromVillageId); + const senderController = this.factory.createController(fromVillageId); + const senderStorage = this.factory.createStorage(fromVillageId); + + const recipientController = this.factory.createController(toVillageId); + + const multiplier = senderState.settings.sendResourcesMultiplier; + const senderReadyResources = senderController.getAvailableForSendResources().downTo(multiplier); + const recipientNeedResources = recipientController.getRequiredResources().upTo(multiplier); + const contractResources = senderReadyResources.min(recipientNeedResources); + + const merchantsInfo = senderStorage.getMerchantsInfo(); + const merchantsCapacity = merchantsInfo.available * merchantsInfo.carry; + + let readyToTransfer = contractResources; + if (contractResources.amount() && contractResources.amount() > merchantsCapacity) { + const merchantScale = merchantsCapacity / contractResources.amount(); + readyToTransfer = contractResources.scale(merchantScale).downTo(multiplier); + } + + console.log('Merchants capacity', merchantsCapacity); + + console.table([ + { name: 'Sender', ...senderReadyResources }, + { name: 'Recipient', ...recipientNeedResources }, + { name: 'Prepared', ...contractResources }, + { name: 'Ready to transfer', ...readyToTransfer }, + ]); + + return { fromVillageId, toVillageId, resources: readyToTransfer, score: readyToTransfer.amount() }; + } +} diff --git a/src/Scheduler.ts b/src/Scheduler.ts index 0b36091..7958788 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -1,4 +1,4 @@ -import { timestamp } from './utils'; +import { around, timestamp } from './utils'; import { TaskQueue } from './Queue/TaskQueue'; import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; @@ -22,11 +22,11 @@ export interface NextExecution { } export class Scheduler { - private taskQueue: TaskQueue; - private actionQueue: ActionQueue; - private villageRepository: VillageRepositoryInterface; - private villageControllerFactory: VillageFactory; - private logger: Logger; + private readonly taskQueue: TaskQueue; + private readonly actionQueue: ActionQueue; + private readonly villageRepository: VillageRepositoryInterface; + private readonly villageControllerFactory: VillageFactory; + private readonly logger: Logger; constructor( taskQueue: TaskQueue, @@ -51,14 +51,16 @@ export class Scheduler { } this.createUniqTaskTimer(5 * 60, GrabVillageState.name); + this.createUniqTaskTimer(10 * 60, SendResourcesTask.name); this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name); this.createUniqTaskTimer(20 * 60, UpdateResourceContracts.name); - this.createUniqTaskTimer(60 * 60, SendOnAdventureTask.name); + // this.createUniqTaskTimer(60 * 60, SendOnAdventureTask.name); } private createUniqTaskTimer(seconds: number, name: string, args: Args = {}) { this.scheduleUniqTask(name, args, timestamp() + seconds - 10); - setInterval(() => this.scheduleUniqTask(name, args, timestamp()), seconds * 1000); + const intervalTime = around(seconds, 0.2) * 1000; + setInterval(() => this.scheduleUniqTask(name, args, timestamp()), intervalTime); } getTaskItems(): ImmutableTaskList { diff --git a/src/Statistics.ts b/src/Statistics.ts index 30185f8..2626d02 100644 --- a/src/Statistics.ts +++ b/src/Statistics.ts @@ -13,20 +13,20 @@ export interface StatisticsStorageInterface { } export class Statistics { - private state: StatisticsStorageInterface; + private readonly storage: StatisticsStorageInterface; static readonly keepRecords = KEEP_RECORD_COUNT; constructor(storage: StatisticsStorageInterface) { - this.state = storage; + this.storage = storage; } incrementAction(ts: number): void { - const stat = this.state.getActionStatistics(); + const stat = this.storage.getActionStatistics(); const key = dateFormat(ts * 1000, KEY_FORMAT); stat[key] = (stat[key] || 0) + 1; this.trimStatistics(stat); - this.state.setActionStatistics(stat); + this.storage.setActionStatistics(stat); } private trimStatistics(stat: ActionStatistics) { @@ -49,6 +49,6 @@ export class Statistics { } getActionStatistics(): ActionStatistics { - return this.state.getActionStatistics(); + return this.storage.getActionStatistics(); } } diff --git a/src/Storage/ResourceTransferStorage.ts b/src/Storage/ResourceTransferStorage.ts new file mode 100644 index 0000000..56809cd --- /dev/null +++ b/src/Storage/ResourceTransferStorage.ts @@ -0,0 +1,33 @@ +import { DataStorage } from '../DataStorage'; +import { ResourceTransferReport } from '../ResourceTransfer'; + +const NAMESPACE = 'resource_transfer.v1'; + +const REPORT_KEY = 'report'; + +export class ResourceTransferStorage { + private storage: DataStorage; + constructor() { + this.storage = new DataStorage(NAMESPACE); + } + + storeReport(report: ResourceTransferReport): void { + this.storage.set(REPORT_KEY, report); + } + + getReport(): ResourceTransferReport { + return this.storage.getTyped(REPORT_KEY, { + factory: () => ({ + fromVillageId: 0, + toVillageId: 0, + resources: { + lumber: 0, + clay: 0, + iron: 0, + crop: 0, + }, + score: 0, + }), + }); + } +} diff --git a/src/Storage/VillageStorage.ts b/src/Storage/VillageStorage.ts index 3c7fd89..0a4a6a4 100644 --- a/src/Storage/VillageStorage.ts +++ b/src/Storage/VillageStorage.ts @@ -2,7 +2,7 @@ import { DataStorage } from '../DataStorage'; import { BuildingQueueInfo } from '../Game'; import { Resources, ResourcesInterface } from '../Core/Resources'; import { ResourceStorage } from '../Core/ResourceStorage'; -import { IncomingMerchant } from '../Core/Market'; +import { IncomingMerchant, MerchantsInfo } from '../Core/Market'; import { VillageSettings, VillageSettingsDefaults } from '../Core/Village'; import { ProductionQueue } from '../Core/ProductionQueue'; import { getNumber } from '../utils'; @@ -13,6 +13,7 @@ const CAPACITY_KEY = 'capacity'; const PERFORMANCE_KEY = 'performance'; const BUILDING_QUEUE_INFO_KEY = 'building_queue_info'; const INCOMING_MERCHANTS_KEY = 'incoming_merchants'; +const MERCHANTS_INFO_KEY = 'merchants_info'; const SETTINGS_KEY = 'settings'; const QUEUE_ENDING_TIME_KEY = 'queue_ending_time'; const TASK_LIST_KEY = 'tasks'; @@ -63,6 +64,16 @@ export class VillageStorage { return Object.assign(res, plain) as BuildingQueueInfo; } + storeMerchantsInfo(info: MerchantsInfo): void { + this.storage.set(MERCHANTS_INFO_KEY, info); + } + + getMerchantsInfo(): MerchantsInfo { + return this.storage.getTyped(MERCHANTS_INFO_KEY, { + factory: () => ({ available: 0, carry: 0 }), + }); + } + storeIncomingMerchants(merchants: ReadonlyArray): void { this.storage.set( INCOMING_MERCHANTS_KEY, diff --git a/src/Task/SendResourcesTask.ts b/src/Task/SendResourcesTask.ts index da04b9f..e4ecb9b 100644 --- a/src/Task/SendResourcesTask.ts +++ b/src/Task/SendResourcesTask.ts @@ -4,21 +4,23 @@ import { ClickButtonAction } from '../Action/ClickButtonAction'; import { goToMarketSendResourcesPage, goToResourceViewPage } from './ActionBundles'; import { Task } from '../Queue/TaskProvider'; import { registerTask } from './TaskMap'; -import { taskError } from '../Errors'; +import { FindSendResourcesPath } from '../Action/FindSendResourcesPath'; @registerTask() export class SendResourcesTask extends TaskController { defineActions(task: Task): Array { - const targetVillageId = task.args.targetVillageId || taskError('Empty target village id'); - const villageId = task.args.villageId || taskError('Empty village id'); + const actions: Array = []; - return [ - goToResourceViewPage(targetVillageId), - goToMarketSendResourcesPage(targetVillageId), - goToResourceViewPage(villageId), - goToMarketSendResourcesPage(villageId), - [SendResourcesAction.name], - [ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }], - ]; + const villages = this.factory.getAllVillages(); + for (let village of villages) { + actions.push(goToResourceViewPage(village.id)); + actions.push(goToMarketSendResourcesPage(village.id)); + } + + actions.push([FindSendResourcesPath.name]); + actions.push([SendResourcesAction.name]); + actions.push([ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }]); + + return actions; } } diff --git a/src/Task/TaskController.ts b/src/Task/TaskController.ts index 1fe8bfe..b87af97 100644 --- a/src/Task/TaskController.ts +++ b/src/Task/TaskController.ts @@ -3,14 +3,17 @@ import { CompleteTaskAction } from '../Action/CompleteTaskAction'; import { Action } from '../Queue/ActionQueue'; import { Args } from '../Queue/Args'; import { Task } from '../Queue/TaskProvider'; +import { VillageFactory } from '../VillageFactory'; export type ActionDefinition = [string] | [string, Args]; export class TaskController { - protected scheduler: Scheduler; + protected readonly scheduler: Scheduler; + protected readonly factory: VillageFactory; - constructor(scheduler: Scheduler) { + constructor(scheduler: Scheduler, factory: VillageFactory) { this.scheduler = scheduler; + this.factory = factory; } async run(task: Task) { diff --git a/src/Task/TaskMap.ts b/src/Task/TaskMap.ts index d7d10b5..4fedc94 100644 --- a/src/Task/TaskMap.ts +++ b/src/Task/TaskMap.ts @@ -1,6 +1,7 @@ import { Scheduler } from '../Scheduler'; import { TaskController } from './TaskController'; import { ProductionQueue } from '../Core/ProductionQueue'; +import { VillageFactory } from '../VillageFactory'; interface TaskOptions { queue?: ProductionQueue; @@ -34,11 +35,15 @@ export function getProductionQueue(name: string): ProductionQueue | undefined { return taskDescription.queue; } -export function createTaskHandler(name: string, scheduler: Scheduler): TaskController | undefined { +export function createTaskHandler( + name: string, + scheduler: Scheduler, + factory: VillageFactory +): TaskController | undefined { const taskDescription = taskMap[name]; if (taskDescription === undefined) { return undefined; } const constructor = (taskDescription.ctor as unknown) as typeof TaskController; - return new constructor(scheduler); + return new constructor(scheduler, factory); } diff --git a/src/VillageController.ts b/src/VillageController.ts index 3b45118..62d0d34 100644 --- a/src/VillageController.ts +++ b/src/VillageController.ts @@ -2,10 +2,13 @@ import { VillageTaskCollection } from './VillageTaskCollection'; import { Task, TaskId } from './Queue/TaskProvider'; import { Args } from './Queue/Args'; import { VillageState } from './VillageState'; +import { Resources } from './Core/Resources'; +import { TryLaterError } from './Errors'; +import { aroundMinutes } from './utils'; export class VillageController { private readonly villageId: number; - private taskCollection: VillageTaskCollection; + private readonly taskCollection: VillageTaskCollection; private readonly state: VillageState; constructor(villageId: number, taskCollection: VillageTaskCollection, state: VillageState) { @@ -33,4 +36,46 @@ export class VillageController { postponeTask(taskId: TaskId, seconds: number) { this.taskCollection.postponeTask(taskId, seconds); } + + getAvailableForSendResources(): Resources { + const balance = this.state.required.balance; + const free = balance.max(Resources.zero()); + + console.table([ + { name: 'Sender balance', ...balance }, + { name: 'Sender free', ...free }, + ]); + + const amount = free.amount(); + const threshold = this.state.settings.sendResourcesThreshold; + + if (amount < threshold) { + return Resources.zero(); + } + + return free; + } + + getRequiredResources(): Resources { + const performance = this.state.performance; + const maxPossibleToStore = this.state.storage.capacity.sub(performance); + const currentResources = this.state.resources; + const incomingResources = this.state.incomingResources; + const requirementResources = this.state.required.resources; + const missingResources = requirementResources + .min(maxPossibleToStore) + .sub(incomingResources) + .sub(currentResources) + .max(Resources.zero()); + + console.table([ + { name: 'Recipient max possible', ...maxPossibleToStore }, + { name: 'Recipient resources', ...currentResources }, + { name: 'Recipient incoming', ...incomingResources }, + { name: 'Recipient requirements', ...requirementResources }, + { name: 'Recipient missing', ...missingResources }, + ]); + + return missingResources; + } } diff --git a/src/VillageFactory.ts b/src/VillageFactory.ts index fce1435..ec31c1b 100644 --- a/src/VillageFactory.ts +++ b/src/VillageFactory.ts @@ -3,6 +3,7 @@ import { VillageStorage } from './Storage/VillageStorage'; import { VillageRepository } from './VillageRepository'; import { VillageTaskCollection } from './VillageTaskCollection'; import { VillageState, VillageStateFactory } from './VillageState'; +import { Village } from './Core/Village'; export class VillageFactory { private readonly villageRepository: VillageRepository; @@ -11,6 +12,14 @@ export class VillageFactory { this.villageRepository = villageRepository; } + getAllVillages(): Array { + return this.villageRepository.all(); + } + + getVillage(villageId: number): Village { + return this.villageRepository.get(villageId); + } + createStorage(villageId: number): VillageStorage { const village = this.villageRepository.get(villageId); return new VillageStorage(village.id); diff --git a/src/utils.ts b/src/utils.ts index 08cb2cd..9c6727a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,6 +10,11 @@ export function randomInRange(from: number, to: number): number { return Math.floor(from + variation); } +export function around(value: number, koeff: number): number { + const delta = Math.floor(value * koeff); + return randomInRange(value - delta, value + delta); +} + export async function sleepMicro() { const timeInMs = randomInRange(1500, 2500); return await sleep(timeInMs); @@ -17,8 +22,7 @@ export async function sleepMicro() { export function aroundMinutes(minutes: number) { const seconds = minutes * 60; - const delta = Math.floor(seconds * 0.1); - return randomInRange(seconds - delta, seconds + delta); + return around(seconds, 0.1); } export async function waitForLoad() {